{"id":23360,"url":"https://patchwork.libcamera.org/api/patches/23360/?format=json","web_url":"https://patchwork.libcamera.org/patch/23360/","project":{"id":1,"url":"https://patchwork.libcamera.org/api/projects/1/?format=json","name":"libcamera","link_name":"libcamera","list_id":"libcamera_core","list_email":"libcamera-devel@lists.libcamera.org","web_url":"","scm_url":"","webscm_url":""},"msgid":"<20250510141220.54872-9-hdegoede@redhat.com>","date":"2025-05-10T14:12:20","name":"[v2,8/8] libcamera: Add new atomisp pipeline handler","commit_ref":null,"pull_url":null,"state":"new","archived":false,"hash":"df5a44b47620808030e4e1199d7cc37abd5be946","submitter":{"id":102,"url":"https://patchwork.libcamera.org/api/people/102/?format=json","name":"Hans de Goede","email":"hdegoede@redhat.com"},"delegate":null,"mbox":"https://patchwork.libcamera.org/patch/23360/mbox/","series":[{"id":5169,"url":"https://patchwork.libcamera.org/api/series/5169/?format=json","web_url":"https://patchwork.libcamera.org/project/libcamera/list/?series=5169","date":"2025-05-10T14:12:12","name":"libcamera: Add swstats_cpu::processFrame() and atomisp pipeline handler","version":2,"mbox":"https://patchwork.libcamera.org/series/5169/mbox/"}],"comments":"https://patchwork.libcamera.org/api/patches/23360/comments/","check":"pending","checks":"https://patchwork.libcamera.org/api/patches/23360/checks/","tags":{},"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 3D948C3226\n\tfor <parsemail@patchwork.libcamera.org>;\n\tSat, 10 May 2025 14:12:54 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id EC22568C91;\n\tSat, 10 May 2025 16:12:53 +0200 (CEST)","from us-smtp-delivery-124.mimecast.com\n\t(us-smtp-delivery-124.mimecast.com [170.10.129.124])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 07F2168C8E\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tSat, 10 May 2025 16:12:52 +0200 (CEST)","from mx-prod-mc-06.mail-002.prod.us-west-2.aws.redhat.com\n\t(ec2-35-165-154-97.us-west-2.compute.amazonaws.com [35.165.154.97])\n\tby relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3,\n\tcipher=TLS_AES_256_GCM_SHA384) id us-mta-7-mSfpAOUxO5ahfqeJgF2GxA-1;\n\tSat, 10 May 2025 10:12:47 -0400","from mx-prod-int-06.mail-002.prod.us-west-2.aws.redhat.com\n\t(mx-prod-int-06.mail-002.prod.us-west-2.aws.redhat.com\n\t[10.30.177.93])\n\t(using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits)\n\tkey-exchange X25519 server-signature RSA-PSS (2048 bits)\n\tserver-digest SHA256) (No client certificate requested)\n\tby mx-prod-mc-06.mail-002.prod.us-west-2.aws.redhat.com (Postfix)\n\twith ESMTPS\n\tid 64B5F180045B for <libcamera-devel@lists.libcamera.org>;\n\tSat, 10 May 2025 14:12:44 +0000 (UTC)","from localhost.localdomain (unknown [10.45.224.58])\n\tby mx-prod-int-06.mail-002.prod.us-west-2.aws.redhat.com (Postfix)\n\twith ESMTP id AF2391800359; Sat, 10 May 2025 14:12:42 +0000 (UTC)"],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (1024-bit key;\n\tunprotected) header.d=redhat.com header.i=@redhat.com\n\theader.b=\"cfuJPzCW\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com;\n\ts=mimecast20190719; t=1746886370;\n\th=from:from:reply-to:subject:subject:date:date:message-id:message-id:\n\tto:to:cc:cc:mime-version:mime-version:content-type:content-type:\n\tcontent-transfer-encoding:content-transfer-encoding:\n\tin-reply-to:in-reply-to:references:references;\n\tbh=LsqNI+ZkMP6wl7CNTCsZUrK4ini8JqT9CVOxSW+Ks+A=;\n\tb=cfuJPzCWG0M2Zuhp+dri0OGe27APeGe1RNtjFqvJTgXozFX5j0Um/BJegrMYb7YcJTnAjX\n\tenSQi16L+wRFNey7d5ClGIcrPB80QAq/wjMatkbtdGBV7l2wI8fiKiz5agZsTOgvSUl8Tx\n\tjNGKnWP+RDJjwn67aeBwC01iEf2Em60=","X-MC-Unique":"mSfpAOUxO5ahfqeJgF2GxA-1","X-Mimecast-MFC-AGG-ID":"mSfpAOUxO5ahfqeJgF2GxA_1746886364","From":"Hans de Goede <hdegoede@redhat.com>","To":"libcamera-devel@lists.libcamera.org","Cc":"Milan Zamazal <mzamazal@redhat.com>, Hans de Goede <hdegoede@redhat.com>","Subject":"[PATCH v2 8/8] libcamera: Add new atomisp pipeline handler","Date":"Sat, 10 May 2025 16:12:20 +0200","Message-ID":"<20250510141220.54872-9-hdegoede@redhat.com>","In-Reply-To":"<20250510141220.54872-1-hdegoede@redhat.com>","References":"<20250510141220.54872-1-hdegoede@redhat.com>","MIME-Version":"1.0","X-Scanned-By":"MIMEDefang 3.4.1 on 10.30.177.93","X-Mimecast-Spam-Score":"0","X-Mimecast-MFC-PROC-ID":"-rjG5YV0POfM_SKxXEHQVklx9HQQlwHJdFi30z8ZtME_1746886364","X-Mimecast-Originator":"redhat.com","Content-Transfer-Encoding":"8bit","content-type":"text/plain; charset=\"US-ASCII\"; x-default=true","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>"},"content":"Add a basic atomisp pipeline handler which supports configuring\nthe pipeline, capturing frames and selecting front/back sensor.\n\nThe atomisp ISP needs some extra lines/columns when debayering and also\nhas some max resolution limitations, this causes the available output\nresolutions to differ from the sensor resolutions.\n\nThe atomisp driver's Android heritage means that it mostly works as a non\nmedia-controller centric v4l2 device, primarily controlled through its\n/dev/video# node. The driver takes care of setting up the pipeline itself\npropagating try / set fmt calls down from its single /dev/video# node to\nthe selected sensor taking the necessary padding, etc. into account.\n\nTherefor things like getting the list of support formats / sizes and\nsetFmt() calls are all done on the /dev/video# node instead of on subdevs,\nthis avoids having to duplicate the padding, etc. logic in the pipeline\nhandler.\n\nSince the statistics buffers which we get from the ISP2 are not documented\nthis uses the swstats_cpu and simple-IPA from the swisp. At the moment only\naec/agc is supported.\n\nawb support will be added in a follow-up patch.\n\nSigned-off-by: Hans de Goede <hdegoede@redhat.com>\n---\n meson.build                                   |   1 +\n meson_options.txt                             |   1 +\n src/ipa/simple/data/uncalibrated_atomisp.yaml |   7 +\n src/libcamera/pipeline/atomisp/atomisp.cpp    | 636 ++++++++++++++++++\n src/libcamera/pipeline/atomisp/meson.build    |   5 +\n src/libcamera/software_isp/meson.build        |   2 +-\n 6 files changed, 651 insertions(+), 1 deletion(-)\n create mode 100644 src/ipa/simple/data/uncalibrated_atomisp.yaml\n create mode 100644 src/libcamera/pipeline/atomisp/atomisp.cpp\n create mode 100644 src/libcamera/pipeline/atomisp/meson.build","diff":"diff --git a/meson.build b/meson.build\nindex 9ba5e2ca..5c4981d8 100644\n--- a/meson.build\n+++ b/meson.build\n@@ -211,6 +211,7 @@ wanted_pipelines = get_option('pipelines')\n arch_arm = ['arm', 'aarch64']\n arch_x86 = ['x86', 'x86_64']\n pipelines_support = {\n+    'atomisp':      arch_x86,\n     'imx8-isi':     arch_arm,\n     'ipu3':         arch_x86,\n     'mali-c55':     arch_arm,\ndiff --git a/meson_options.txt b/meson_options.txt\nindex 2104469e..c7051ee7 100644\n--- a/meson_options.txt\n+++ b/meson_options.txt\n@@ -47,6 +47,7 @@ option('pipelines',\n         value : ['auto'],\n         choices : [\n             'all',\n+            'atomisp',\n             'auto',\n             'imx8-isi',\n             'ipu3',\ndiff --git a/src/ipa/simple/data/uncalibrated_atomisp.yaml b/src/ipa/simple/data/uncalibrated_atomisp.yaml\nnew file mode 100644\nindex 00000000..6dcc0295\n--- /dev/null\n+++ b/src/ipa/simple/data/uncalibrated_atomisp.yaml\n@@ -0,0 +1,7 @@\n+# SPDX-License-Identifier: CC0-1.0\n+%YAML 1.1\n+---\n+version: 1\n+algorithms:\n+  - Agc:\n+...\ndiff --git a/src/libcamera/pipeline/atomisp/atomisp.cpp b/src/libcamera/pipeline/atomisp/atomisp.cpp\nnew file mode 100644\nindex 00000000..959c3f0d\n--- /dev/null\n+++ b/src/libcamera/pipeline/atomisp/atomisp.cpp\n@@ -0,0 +1,636 @@\n+/* SPDX-License-Identifier: LGPL-2.1-or-later */\n+/*\n+ * atomisp.cpp - Pipeline handler for atomisp devices\n+ *\n+ * The atomisp ISP needs some extra lines/columns when debayering and also has\n+ * some max resolution limitations, this causes the available output resolutions\n+ * to differ from the sensor resolutions.\n+ *\n+ * The atomisp driver's Android heritage means that it mostly works as a non\n+ * media-controller centric v4l2 device, primarily controlled through its\n+ * /dev/video# node. The driver takes care of setting up the pipeline itself\n+ * propagating try / set fmt calls down from its single /dev/video# node to\n+ * the selected sensor taking the necessary padding, etc. into account.\n+ *\n+ * Therefor things like getting the list of support formats / sizes and tryFmt()\n+ * / setFmt() calls are all done on the /dev/video# node instead of on subdevs,\n+ * this avoids having to duplicate the padding, etc. logic here.\n+ * Note this requires enabling the ISP <-> CSI receiver for the current sensor\n+ * even when only querying / trying formats.\n+ *\n+ * Copyright (C) 2024, Hans de Goede <hansg@kernel.org>\n+ *\n+ * Partially based on simple.cpp and uvcvideo.cpp which are:\n+ * Copyright (C) 2020, Laurent Pinchart\n+ * Copyright (C) 2019, Martijn Braam\n+ * Copyright (C) 2019, Google Inc.\n+ */\n+\n+#include <algorithm>\n+#include <map>\n+#include <memory>\n+#include <set>\n+#include <string>\n+#include <unordered_map>\n+#include <utility>\n+#include <vector>\n+\n+#include <libcamera/base/log.h>\n+#include <libcamera/base/utils.h>\n+\n+#include <libcamera/camera.h>\n+#include <libcamera/control_ids.h>\n+#include <libcamera/controls.h>\n+#include <libcamera/formats.h>\n+#include <libcamera/property_ids.h>\n+#include <libcamera/request.h>\n+#include <libcamera/stream.h>\n+\n+#include <libcamera/ipa/soft_ipa_interface.h>\n+#include <libcamera/ipa/soft_ipa_proxy.h>\n+\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/ipa_manager.h\"\n+#include \"libcamera/internal/media_device.h\"\n+#include \"libcamera/internal/pipeline_handler.h\"\n+#include \"libcamera/internal/software_isp/debayer_params.h\"\n+#include \"libcamera/internal/software_isp/swstats_cpu.h\"\n+#include \"libcamera/internal/v4l2_videodevice.h\"\n+\n+namespace libcamera {\n+\n+LOG_DEFINE_CATEGORY(Atomisp)\n+\n+class AtomispCameraData : public Camera::Private\n+{\n+public:\n+\tAtomispCameraData(PipelineHandler *pipe, V4L2VideoDevice *video)\n+\t\t: Camera::Private(pipe), video_(video)\n+\t{\n+\t}\n+\n+\tint init(MediaEntity *sensor);\n+\tvoid imageBufferReady(FrameBuffer *buffer);\n+\tvoid statsReady(uint32_t frame, uint32_t bufferId);\n+\tvoid setSensorControls(const ControlList &sensorControls);\n+\n+\t/* This is owned by AtomispPipelineHandler and shared by the cameras */\n+\tV4L2VideoDevice *video_;\n+\tstd::unique_ptr<CameraSensor> sensor_;\n+\tstd::unique_ptr<DelayedControls> delayedCtrls_;\n+\tstd::unique_ptr<SwStatsCpu> stats_;\n+\tstd::unique_ptr<ipa::soft::IPAProxySoft> ipa_;\n+\tSharedMemObject<DebayerParams> debayerParams_;\n+\tStream stream_;\n+\tstd::map<PixelFormat, std::vector<SizeRange>> formats_;\n+\tMediaLink *csiReceiverIspLink_;\n+};\n+\n+class AtomispCameraConfiguration : public CameraConfiguration\n+{\n+public:\n+\tAtomispCameraConfiguration()\n+\t\t: CameraConfiguration() {}\n+\n+\tStatus validate() override;\n+};\n+\n+class AtomispPipelineHandler : public PipelineHandler\n+{\n+public:\n+\tAtomispPipelineHandler(CameraManager *manager)\n+\t\t: PipelineHandler(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+\tbool match(DeviceEnumerator *enumerator) override;\n+\n+private:\n+\tbool acquireDevice(Camera *camera) override;\n+\tvoid releaseDevice(Camera *camera) override;\n+\n+\tAtomispCameraData *cameraData(Camera *camera)\n+\t{\n+\t\treturn static_cast<AtomispCameraData *>(camera->_d());\n+\t}\n+\n+\tstd::unique_ptr<V4L2VideoDevice> video_;\n+};\n+\n+bool AtomispPipelineHandler::acquireDevice(Camera *camera)\n+{\n+\tAtomispCameraData *data = cameraData(camera);\n+\n+\t/* atomisp can run only 1 sensor / camera at a time */\n+\tif (data->video_->isOpen())\n+\t\treturn false;\n+\n+\tint ret = data->video_->open();\n+\tif (ret != 0)\n+\t\treturn false;\n+\n+\tret = data->csiReceiverIspLink_->setEnabled(true);\n+\tif (ret) {\n+\t\tdata->video_->close();\n+\t\treturn false;\n+\t}\n+\n+\treturn true;\n+}\n+\n+void AtomispPipelineHandler::releaseDevice(Camera *camera)\n+{\n+\tAtomispCameraData *data = cameraData(camera);\n+\n+\tdata->csiReceiverIspLink_->setEnabled(false);\n+\tdata->video_->close();\n+}\n+\n+CameraConfiguration::Status AtomispCameraConfiguration::validate()\n+{\n+\tStatus status = Valid;\n+\n+\tif (config_.empty())\n+\t\treturn Invalid;\n+\n+\tif (orientation != Orientation::Rotate0) {\n+\t\torientation = Orientation::Rotate0;\n+\t\tstatus = Adjusted;\n+\t}\n+\n+\t/* Cap the number of entries to the available streams. */\n+\tif (config_.size() > 1) {\n+\t\tconfig_.resize(1);\n+\t\tstatus = Adjusted;\n+\t}\n+\n+\tStreamConfiguration &cfg = config_[0];\n+\tconst StreamFormats &formats = cfg.formats();\n+\tconst PixelFormat pixelFormat = cfg.pixelFormat;\n+\tconst Size size = cfg.size;\n+\n+\tconst std::vector<PixelFormat> pixelFormats = formats.pixelformats();\n+\tauto iter = std::find(pixelFormats.begin(), pixelFormats.end(), pixelFormat);\n+\tif (iter == pixelFormats.end()) {\n+\t\tcfg.pixelFormat = pixelFormats.front();\n+\t\tLOG(Atomisp, Debug)\n+\t\t\t<< \"Adjusting pixel format from \" << pixelFormat\n+\t\t\t<< \" to \" << cfg.pixelFormat;\n+\t\tstatus = Adjusted;\n+\t}\n+\n+\tconst std::vector<Size> &formatSizes = formats.sizes(cfg.pixelFormat);\n+\tcfg.size = formatSizes.front();\n+\tfor (const Size &formatsSize : formatSizes) {\n+\t\tif (formatsSize > size)\n+\t\t\tbreak;\n+\n+\t\tcfg.size = formatsSize;\n+\t}\n+\n+\tif (cfg.size != size) {\n+\t\tLOG(Atomisp, Debug)\n+\t\t\t<< \"Adjusting size from \" << size << \" to \" << cfg.size;\n+\t\tstatus = Adjusted;\n+\t}\n+\n+\t/*\n+\t * The atomisp has an internal pipeline length of 3 frames,\n+\t * it generates 3A statistics info 1 - 2 frames before generating\n+\t * the video buffer with the final image.\n+\t *\n+\t * We need to make sure this pipeline is fed with buffers all\n+\t * the time since the firmware simply asserts on buffer underruns\n+\t * after which the driver has to recover by stopping streaming,\n+\t * resetting the ISP and then starting the stream again.\n+\t *\n+\t * Use a buffercount of 8 so that we can have 1 - 3 buffers waiting\n+\t * on userspace while still having 2 reserve buffers owned by\n+\t * the kernel + 3 buffers owned by the ISP.\n+\t */\n+\tcfg.bufferCount = 8;\n+\n+\tswitch (cfg.pixelFormat) {\n+\tcase formats::YUV420:\n+\t\t/* atomisp stride must be a multiple of 32 */\n+\t\tcfg.stride = (cfg.size.width + 31) & ~31;\n+\t\tcfg.frameSize = cfg.stride * cfg.size.height * 3 / 2;\n+\t\tbreak;\n+\tdefault:\n+\t\tLOG(Atomisp, Error)\n+\t\t\t<< \"Unknown pixel-format \" << cfg.pixelFormat;\n+\t\treturn Invalid;\n+\t}\n+\n+\tif (cfg.colorSpace != ColorSpace::Rec709) {\n+\t\tcfg.colorSpace = ColorSpace::Rec709;\n+\t\tstatus = Adjusted;\n+\t}\n+\n+\treturn status;\n+}\n+\n+std::unique_ptr<CameraConfiguration>\n+AtomispPipelineHandler::generateConfiguration(Camera *camera,\n+\t\t\t\t\t      Span<const StreamRole> roles)\n+{\n+\tAtomispCameraData *data = cameraData(camera);\n+\tstd::unique_ptr<CameraConfiguration> config =\n+\t\tstd::make_unique<AtomispCameraConfiguration>();\n+\n+\tif (roles.empty())\n+\t\treturn config;\n+\n+\tStreamFormats formats(data->formats_);\n+\tStreamConfiguration cfg(formats);\n+\n+\tcfg.pixelFormat = formats.pixelformats().front();\n+\tcfg.size = formats.sizes(cfg.pixelFormat).back();\n+\tcfg.bufferCount = 4;\n+\n+\tconfig->addConfiguration(cfg);\n+\n+\tconfig->validate();\n+\n+\treturn config;\n+}\n+\n+int AtomispPipelineHandler::configure(Camera *camera, CameraConfiguration *config)\n+{\n+\tAtomispCameraData *data = cameraData(camera);\n+\tStreamConfiguration &cfg = config->at(0);\n+\n+\tint ret = data->stats_->configure(cfg);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tipa::soft::IPAConfigInfo configInfo;\n+\tconfigInfo.sensorControls = data->sensor_->controls();\n+\n+\tret = data->ipa_->configure(configInfo);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tV4L2PixelFormat fourcc = data->video_->toV4L2PixelFormat(cfg.pixelFormat);\n+\tV4L2DeviceFormat format;\n+\n+\tformat.size = cfg.size;\n+\tformat.fourcc = fourcc;\n+\tret = data->video_->setFormat(&format);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tif (format.size != cfg.size) {\n+\t\tLOG(Atomisp, Error)\n+\t\t\t<< \"format mismatch req \" << cfg.size << \" got \" << format.size;\n+\t\treturn -EINVAL;\n+\t}\n+\n+\tif (format.fourcc != fourcc) {\n+\t\tLOG(Atomisp, Error)\n+\t\t\t<< \"format mismatch req \" << fourcc << \" got \" << format.fourcc;\n+\t\treturn -EINVAL;\n+\t}\n+\n+\tdata->stats_->setWindow(Rectangle(cfg.size));\n+\n+\tcfg.setStream(&data->stream_);\n+\n+\tstd::unordered_map<uint32_t, DelayedControls::ControlParams> params = {\n+\t\t{ V4L2_CID_ANALOGUE_GAIN, { 2, false } },\n+\t\t{ V4L2_CID_EXPOSURE, { 2, false } },\n+\t};\n+\tdata->delayedCtrls_ =\n+\t\tstd::make_unique<DelayedControls>(data->sensor_->device(),\n+\t\t\t\t\t\t  params);\n+\tdata->video_->frameStart.connect(data->delayedCtrls_.get(),\n+\t\t\t\t\t &DelayedControls::applyControls);\n+\treturn 0;\n+}\n+\n+int AtomispPipelineHandler::exportFrameBuffers(Camera *camera, Stream *stream,\n+\t\t\t\t\t       std::vector<std::unique_ptr<FrameBuffer>> *buffers)\n+{\n+\tAtomispCameraData *data = cameraData(camera);\n+\tunsigned int count = stream->configuration().bufferCount;\n+\n+\treturn data->video_->exportBuffers(count, buffers);\n+}\n+\n+int AtomispPipelineHandler::start(Camera *camera, [[maybe_unused]] const ControlList *controls)\n+{\n+\tAtomispCameraData *data = cameraData(camera);\n+\tunsigned int count = data->stream_.configuration().bufferCount;\n+\tint ret;\n+\n+\tret = data->video_->importBuffers(count);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tvideo_->bufferReady.connect(data, &AtomispCameraData::imageBufferReady);\n+\n+\tret = data->video_->streamOn();\n+\tif (ret) {\n+\t\tvideo_->bufferReady.disconnect(data, &AtomispCameraData::imageBufferReady);\n+\t\tdata->video_->releaseBuffers();\n+\t\treturn ret;\n+\t}\n+\n+\tret = data->ipa_->start();\n+\tif (ret) {\n+\t\tdata->video_->streamOff();\n+\t\tvideo_->bufferReady.disconnect(data, &AtomispCameraData::imageBufferReady);\n+\t\tdata->video_->releaseBuffers();\n+\t\treturn ret;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+void AtomispPipelineHandler::stopDevice(Camera *camera)\n+{\n+\tAtomispCameraData *data = cameraData(camera);\n+\n+\tdata->video_->streamOff();\n+\tvideo_->bufferReady.disconnect(data, &AtomispCameraData::imageBufferReady);\n+\tdata->video_->releaseBuffers();\n+\tdata->ipa_->stop();\n+}\n+\n+int AtomispPipelineHandler::queueRequestDevice(Camera *camera, Request *request)\n+{\n+\tAtomispCameraData *data = cameraData(camera);\n+\tFrameBuffer *buffer = request->findBuffer(&data->stream_);\n+\tif (!buffer) {\n+\t\tLOG(Atomisp, Error)\n+\t\t\t<< \"Attempt to queue request with invalid stream\";\n+\n+\t\treturn -ENOENT;\n+\t}\n+\n+\tint ret = data->video_->queueBuffer(buffer);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tdata->ipa_->queueRequest(request->sequence(), request->controls());\n+\treturn 0;\n+}\n+\n+/* -----------------------------------------------------------------------------\n+ * Match and Setup\n+ */\n+bool AtomispPipelineHandler::match(DeviceEnumerator *enumerator)\n+{\n+\tstd::vector<MediaEntity *> sensors;\n+\tMediaEntity *videoEntity = NULL;\n+\tDeviceMatch dm(\"atomisp-isp2\");\n+\tMediaDevice *media;\n+\n+\tmedia = acquireMediaDevice(enumerator, dm);\n+\tif (!media)\n+\t\treturn false;\n+\n+\tfor (MediaEntity *entity : media->entities()) {\n+\t\tswitch (entity->function()) {\n+\t\tcase MEDIA_ENT_F_CAM_SENSOR:\n+\t\t\tsensors.push_back(entity);\n+\t\t\tbreak;\n+\t\tcase MEDIA_ENT_F_IO_V4L:\n+\t\t\tvideoEntity = entity;\n+\t\t\tbreak;\n+\t\t}\n+\t}\n+\n+\tif (!videoEntity) {\n+\t\tLOG(Atomisp, Error) << \"Could not find the video device\";\n+\t\treturn false;\n+\t}\n+\tif (sensors.empty()) {\n+\t\tLOG(Atomisp, Error) << \"No sensor found\";\n+\t\treturn false;\n+\t}\n+\n+\t/* Create and open the video device. */\n+\tvideo_ = std::make_unique<V4L2VideoDevice>(videoEntity);\n+\tint ret = video_->open();\n+\tif (ret)\n+\t\treturn false;\n+\n+\t/* Create and register a camera for each sensor */\n+\tbool registered = false;\n+\tfor (MediaEntity *sensor : sensors) {\n+\t\tstd::unique_ptr<AtomispCameraData> data =\n+\t\t\tstd::make_unique<AtomispCameraData>(this, video_.get());\n+\n+\t\tif (data->init(sensor))\n+\t\t\tcontinue;\n+\n+\t\tconst std::string &id = data->sensor_->id();\n+\t\tstd::set<Stream *> streams{ &data->stream_ };\n+\t\tstd::shared_ptr<Camera> camera =\n+\t\t\tCamera::create(std::move(data), id, streams);\n+\t\tregisterCamera(std::move(camera));\n+\t\tregistered = true;\n+\t}\n+\n+\t/*\n+\t * atomisp cameras share a single /dev/video# node. The shared node\n+\t * gets opened from acquireDevice() to allow only one camera to be\n+\t * acquired at a time.\n+\t * This also works around a kernel bug (which needs to be fixed) where\n+\t * the node needs to be closed for the ISP to runtime-suspend.\n+\t */\n+\tvideo_->close();\n+\n+\treturn registered;\n+}\n+\n+int AtomispCameraData::init(MediaEntity *sensor)\n+{\n+\tMediaEntity *source, *sink, *csi_receiver = NULL;\n+\tconst MediaPad *source_pad;\n+\tint source_pad_idx = 0;\n+\n+\tsensor_ = CameraSensorFactoryBase::create(sensor);\n+\tif (!sensor_)\n+\t\treturn -ENODEV;\n+\n+\tdebayerParams_ = SharedMemObject<DebayerParams>(\"debayer_params\");\n+\tif (!debayerParams_) {\n+\t\tLOG(Atomisp, Error) << \"Failed to create shared memory for parameters\";\n+\t\treturn -ENOMEM;\n+\t}\n+\n+\tstats_ = std::make_unique<SwStatsCpu>();\n+\tif (!stats_->isValid()) {\n+\t\tLOG(Atomisp, Error) << \"Failed to create SwStatsCpu object\";\n+\t\treturn -ENOMEM;\n+\t}\n+\n+\tipa_ = IPAManager::createIPA<ipa::soft::IPAProxySoft>(pipe(), 0, 0, \"simple\");\n+\tif (!ipa_) {\n+\t\tLOG(Atomisp, Error) << \"Creating IPA failed\";\n+\t\treturn -ENOMEM;\n+\t}\n+\n+\t/*\n+\t * The API tuning file is made from the sensor name. If the tuning file\n+\t * isn't found, fall back to the 'uncalibrated' file.\n+\t */\n+\tstd::string ipaTuningFile =\n+\t\tipa_->configurationFile(sensor_->model() + \"_atomisp.yaml\",\n+\t\t\t\t\t\"uncalibrated_atomisp.yaml\");\n+\n+\tIPACameraSensorInfo sensorInfo{};\n+\tint ret = sensor_->sensorInfo(&sensorInfo);\n+\tif (ret) {\n+\t\tLOG(Atomisp, Error) << \"Camera sensor information not available\";\n+\t\treturn -ENODEV;\n+\t}\n+\n+\t/* Passing CCM parameters to the ISP is not support (yet?) */\n+\tbool ccmEnabled = false;\n+\n+\tret = ipa_->init(IPASettings{ ipaTuningFile, sensor_->model() },\n+\t\t\t stats_->getStatsFD(),\n+\t\t\t debayerParams_.fd(),\n+\t\t\t sensorInfo,\n+\t\t\t sensor_->controls(),\n+\t\t\t &controlInfo_,\n+\t\t\t &ccmEnabled);\n+\tif (ret) {\n+\t\tLOG(Atomisp, Error) << \"IPA init failed\";\n+\t\treturn ret;\n+\t}\n+\n+\tsource = sensor;\n+\tfor (int i = 0; i < 2; i++) {\n+\t\tsource_pad = source->getPadByIndex(source_pad_idx);\n+\t\tif (source_pad == nullptr) {\n+\t\t\tLOG(Atomisp, Error)\n+\t\t\t\t<< source << \" doesn't have pad \" << source_pad_idx;\n+\t\t\treturn -ENODEV;\n+\t\t}\n+\n+\t\tsink = source_pad->links()[0]->sink()->entity();\n+\t\tswitch (sink->function()) {\n+\t\tcase MEDIA_ENT_F_VID_IF_BRIDGE:\n+\t\t\t/* Found the CSI2 receiver */\n+\t\t\tcsi_receiver = sink;\n+\t\t\tbreak;\n+\t\tcase MEDIA_ENT_F_PROC_VIDEO_ISP:\n+\t\t\t/*\n+\t\t\t * Sensor with builtin ISP, e.g. MT9M114.\n+\t\t\t * CSI receiver is downstream of this entity.\n+\t\t\t */\n+\t\t\tsource = sink;\n+\t\t\tsource_pad_idx = 1;\n+\t\t\tcontinue;\n+\t\tdefault:\n+\t\t\tLOG(Atomisp, Error)\n+\t\t\t\t<< sink << \" has unexpected function \" << sink->function();\n+\t\t\treturn -ENODEV;\n+\t\t}\n+\t}\n+\tif (!csi_receiver) {\n+\t\tLOG(Atomisp, Error)\n+\t\t\t<< \"Camera \" << sensor_->model() << \" cannot find CSI receiver\";\n+\t\treturn -ENODEV;\n+\t}\n+\n+\tsource_pad = csi_receiver->getPadByIndex(1);\n+\tif (source_pad == nullptr) {\n+\t\tLOG(Atomisp, Error)\n+\t\t\t<< \"CSI receiver for \" << sensor_->model() << \" doesn't have pad1\";\n+\t\treturn -ENODEV;\n+\t}\n+\n+\tcsiReceiverIspLink_ = source_pad->links()[0];\n+\n+\tret = csiReceiverIspLink_->setEnabled(true);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\t/*\n+\t * The atomisp supports many different output formats but SwStatsCpu,\n+\t * used for 3A due to atomisp statistics being undocumented,\n+\t * only supports a few formats.\n+\t */\n+\tstd::vector<PixelFormat> supported({ formats::YUV420 });\n+\n+\tfor (const auto &format : video_->formats()) {\n+\t\tPixelFormat pixelFormat = format.first.toPixelFormat();\n+\n+\t\tif (std::find(supported.begin(), supported.end(), pixelFormat) == supported.end())\n+\t\t\tcontinue;\n+\n+\t\tformats_[pixelFormat] = format.second;\n+\t}\n+\n+\tcsiReceiverIspLink_->setEnabled(false);\n+\n+\tif (formats_.empty()) {\n+\t\tLOG(Atomisp, Error)\n+\t\t\t<< \"Camera \" << sensor_->model()\n+\t\t\t<< \" doesn't expose any supported format\";\n+\t\treturn -EINVAL;\n+\t}\n+\n+\tproperties_ = sensor_->properties();\n+\n+\tstats_->statsReady.connect(this, &AtomispCameraData::statsReady);\n+\tipa_->setSensorControls.connect(this, &AtomispCameraData::setSensorControls);\n+\n+\treturn 0;\n+}\n+\n+void AtomispCameraData::imageBufferReady(FrameBuffer *buffer)\n+{\n+\tRequest *request = buffer->request();\n+\n+\tif (buffer->metadata().status == FrameMetadata::FrameSuccess) {\n+\t\tipa_->computeParams(request->sequence());\n+\n+\t\t/*\n+\t\t * Buffer ids are currently not used, so pass zero as buffer id.\n+\t\t *\n+\t\t * \\todo Pass real bufferId once stats buffer passing is changed.\n+\t\t */\n+\t\tstats_->processFrame(request->sequence(), 0, buffer);\n+\n+\t\trequest->metadata().set(controls::SensorTimestamp,\n+\t\t\t\t\tbuffer->metadata().timestamp);\n+\t}\n+\n+\tpipe()->completeBuffer(request, buffer);\n+\tpipe()->completeRequest(request);\n+}\n+\n+void AtomispCameraData::statsReady(uint32_t frame, uint32_t bufferId)\n+{\n+\tipa_->processStats(frame, bufferId, delayedCtrls_->get(frame));\n+}\n+\n+void AtomispCameraData::setSensorControls(const ControlList &sensorControls)\n+{\n+\tdelayedCtrls_->push(sensorControls);\n+\tControlList ctrls(sensorControls);\n+\tsensor_->setControls(&ctrls);\n+}\n+\n+REGISTER_PIPELINE_HANDLER(AtomispPipelineHandler, \"atomisp\")\n+\n+} /* namespace libcamera */\ndiff --git a/src/libcamera/pipeline/atomisp/meson.build b/src/libcamera/pipeline/atomisp/meson.build\nnew file mode 100644\nindex 00000000..179a35ef\n--- /dev/null\n+++ b/src/libcamera/pipeline/atomisp/meson.build\n@@ -0,0 +1,5 @@\n+# SPDX-License-Identifier: CC0-1.0\n+\n+libcamera_internal_sources += files([\n+    'atomisp.cpp',\n+])\ndiff --git a/src/libcamera/software_isp/meson.build b/src/libcamera/software_isp/meson.build\nindex 59fa5f02..81c4c255 100644\n--- a/src/libcamera/software_isp/meson.build\n+++ b/src/libcamera/software_isp/meson.build\n@@ -1,6 +1,6 @@\n # SPDX-License-Identifier: CC0-1.0\n \n-softisp_enabled = pipelines.contains('simple')\n+softisp_enabled = pipelines.contains('simple') or pipelines.contains('atomisp')\n summary({'SoftISP support' : softisp_enabled}, section : 'Configuration')\n \n if not softisp_enabled\n","prefixes":["v2","8/8"]}