{"id":26512,"url":"https://patchwork.libcamera.org/api/patches/26512/?format=json","web_url":"https://patchwork.libcamera.org/patch/26512/","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":"<20260408115645.12487-2-johannes.goede@oss.qualcomm.com>","date":"2026-04-08T11:56:43","name":"[RFC,1/3] camss: Add CAMSS pipeline handler","commit_ref":null,"pull_url":null,"state":"new","archived":false,"hash":"7dabc2c78776785468be41f0f6315a3aa46ca20a","submitter":{"id":242,"url":"https://patchwork.libcamera.org/api/people/242/?format=json","name":"Hans de Goede","email":"johannes.goede@oss.qualcomm.com"},"delegate":null,"mbox":"https://patchwork.libcamera.org/patch/26512/mbox/","series":[{"id":5878,"url":"https://patchwork.libcamera.org/api/series/5878/?format=json","web_url":"https://patchwork.libcamera.org/project/libcamera/list/?series=5878","date":"2026-04-08T11:56:42","name":"camss: Add CAMSS pipeline handler","version":1,"mbox":"https://patchwork.libcamera.org/series/5878/mbox/"}],"comments":"https://patchwork.libcamera.org/api/patches/26512/comments/","check":"pending","checks":"https://patchwork.libcamera.org/api/patches/26512/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 CD262BDCBD\n\tfor <parsemail@patchwork.libcamera.org>;\n\tWed,  8 Apr 2026 11:56:57 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 7BF2D62E3F;\n\tWed,  8 Apr 2026 13:56:57 +0200 (CEST)","from mx0b-0031df01.pphosted.com (mx0b-0031df01.pphosted.com\n\t[205.220.180.131])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id 821F662E11\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tWed,  8 Apr 2026 13:56:55 +0200 (CEST)","from pps.filterd (m0279870.ppops.net [127.0.0.1])\n\tby mx0a-0031df01.pphosted.com (8.18.1.11/8.18.1.11) with ESMTP id\n\t638BuckU2450557 for <libcamera-devel@lists.libcamera.org>;\n\tWed, 8 Apr 2026 11:56:54 GMT","from mail-qt1-f200.google.com (mail-qt1-f200.google.com\n\t[209.85.160.200])\n\tby mx0a-0031df01.pphosted.com (PPS) with ESMTPS id 4ddae6amph-1\n\t(version=TLSv1.3 cipher=TLS_AES_128_GCM_SHA256 bits=128 verify=NOT)\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tWed, 08 Apr 2026 11:56:54 +0000 (GMT)","by mail-qt1-f200.google.com with SMTP id\n\td75a77b69052e-50d8e8c47a3so67892231cf.0\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tWed, 08 Apr 2026 04:56:54 -0700 (PDT)","from t14s\n\t(2001-1c00-0c32-7800-07d4-cca3-ec08-7ac7.cable.dynamic.v6.ziggo.nl.\n\t[2001:1c00:c32:7800:7d4:cca3:ec08:7ac7])\n\tby smtp.gmail.com with ESMTPSA id\n\t4fb4d7f45d1cf-66e7b5ffa64sm3158932a12.21.2026.04.08.04.56.47\n\t(version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256);\n\tWed, 08 Apr 2026 04:56:48 -0700 (PDT)"],"Authentication-Results":"lancelot.ideasonboard.com; dkim=pass (2048-bit key;\n\tunprotected) header.d=qualcomm.com header.i=@qualcomm.com\n\theader.b=\"O6NeSNNg\"; dkim=pass (2048-bit key;\n\tunprotected) header.d=oss.qualcomm.com header.i=@oss.qualcomm.com\n\theader.b=\"fMwsPm4t\"; dkim-atps=neutral","DKIM-Signature":["v=1; a=rsa-sha256; c=relaxed/relaxed; d=qualcomm.com; h=\n\tcc:content-transfer-encoding:date:from:in-reply-to:message-id\n\t:mime-version:references:subject:to; s=qcppdkim1; bh=UA31l+35P6J\n\t6IAv2+eJwqnV6gYqjz+bmm8ZRU3vz6f0=; b=O6NeSNNg0Ocx/w7YVmXpAgnw3Ai\n\thccgkiNUT0JdVpzJbgTw0K8QSWjhCTBTCiByKMeCf8EyHwPPxF5kIHRCP8Si+BDP\n\te0I4asPLznmcx0ai0zOxXhHDX1Tj6qHC+1DsQ3T1M8/SjLrXxOA8EyPdoRcHql/x\n\tNe0oZ9COdqiIIu4SEEHDQsD27Iwzc+S5Er0A9wK8UOpz40Izl1MgvFTBIanjAMgZ\n\tGbfTwoalJn3eTbl7bm+KV4XXb/GE2N+QtZjaC7Op1DSAmNdMbLHUpkmT0CnxcGnF\n\tB3czucr1h98N3VgJDR5ZvAQjzoo5ft6PjJN9sg42aXXeLtZxqAjn26T+rwg==","v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=oss.qualcomm.com; s=google; t=1775649413; x=1776254213;\n\tdarn=lists.libcamera.org; \n\th=content-transfer-encoding:mime-version:references:in-reply-to\n\t:message-id:date:subject:cc:to:from:from:to:cc:subject:date\n\t:message-id:reply-to;\n\tbh=UA31l+35P6J6IAv2+eJwqnV6gYqjz+bmm8ZRU3vz6f0=;\n\tb=fMwsPm4tbTJAAxJw5LAa4LQgY0Jgfn2QzwEN8TG6yaPFaOJjnlLOC/hh4ofzWjeWwp\n\t06goZE/wjPmW5wAtTIRqFiLqcd25JzGACxbiQ9pQyOcHXzSMNMEWlVO5HJ6o/nZoxLYw\n\t7kxI9nd7wPJeYPT9a/siQrNKEZyFul2PwF78scmFYL8wJ4G0JfQB4UWvz2bDEfUStpP8\n\t3npxbY4Ij1r31iGAOmWLho4k8NXyPgbJr436wCClkcRk3AT8/KTlJe+/3gRs890DQFqj\n\tlgEvgvDRN56VoKh8iXDJLfm5lE+u/QLonU/o/1/0ayL/xViFMR8PyO79ng9ArpjibOt4\n\tVmUg=="],"X-Google-DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20251104; t=1775649413; x=1776254213;\n\th=content-transfer-encoding:mime-version:references:in-reply-to\n\t:message-id:date:subject:cc:to:from:x-gm-gg:x-gm-message-state:from\n\t:to:cc:subject:date:message-id:reply-to;\n\tbh=UA31l+35P6J6IAv2+eJwqnV6gYqjz+bmm8ZRU3vz6f0=;\n\tb=LkxvWs0BN2DQuYuFebUr8G83J6t/vqQ8RuN1pZqAC4juJdGT/x3QNmqY5soBnwtqRL\n\t410+25duU07DsccSpndwI3i9jcUGiQInwd514pGVmcAGc9IJuF0ck3HL2tFIN2v/E2yJ\n\tiRyp+vG9UzLSWEjI4VyYBJdwdKBpZfSzOLTVLisLMhbMRU779JHCtocGoO90IOv+O/qf\n\t1NnV/R239TDl2bHeCKBnsdSN2SwA3fVWo0vNKuv97vhfyALJESO7ET3LpKMpSBCtFFMC\n\t6ZmG8FC6A2yTSSo5nU4D+O1241QUjFhIermOPjTwN969ZIdZg8GOhO4A+G8j8Nzlgh8w\n\teMXg==","X-Gm-Message-State":"AOJu0YzmCpKfnu/RWQvyO40ITEGtKL0JPEevL1Ai00AX1v9a4XIphKwE\n\tWaIpoQ4PWfUC0bY8Q8BIlxwXVz6u45ybNXBlK1uQspvmZTktOg0Jcy9V8omH4mTA8hYXoVC53Wl\n\tZN8FBN3C6w2ehfhQgF3siaSdkmbQ9G7SLEr1u6UM5lRl+IBXx+khK7gTJTPKVvCB4IKbbzPLQOh\n\th0DNGjdlcT","X-Gm-Gg":"AeBDievhjPUU+oSBkWO1+0rq2PMjFT295ilhNUFfUoIPvfxMBJLL3q3Dm5EDA4sYiAm\n\tNKyG3y1VRiAg7vi/zxLU55kjQA3sY9zn50zRrQpQ04Fv65Nb/UQHZIlA6s9uwHqbADj0ZJa1S7L\n\tH+RXFADU/B6RXiGdkI710HKD/wwPkeypcRz78TwtYiVaRoiLEBZgApQHgDgTg72cvoELDaY/yqy\n\tIR9TNywSg+XZoTnrZGL5uOijw8H9BMD/DIsqIJlmpaNLXaRpcoPWInLee9OvEBZuh2xsbkx8Lj9\n\tjT+vgTPh6m1mhY31c6CzNv8RNQAARK9ocuOs909Ywx8uhXfQ/ovEWYQ7JPUQpOo0Kbm8XQQewMd\n\tO9lQsbRs12vvX0BB3oW6Zhf0f7EJBmJWwE2TbLKtC5TpmeV7LIN4jkbm981IozkJwjchzooAb27\n\tLDryIUTTPpY6Un4HxJ/hsS+8A9xXW9uOFw","X-Received":["by 2002:a05:622a:513:b0:50b:2eee:4b38 with SMTP id\n\td75a77b69052e-50d62b4fcfdmr297652101cf.8.1775649412050; \n\tWed, 08 Apr 2026 04:56:52 -0700 (PDT)","by 2002:a05:622a:513:b0:50b:2eee:4b38 with SMTP id\n\td75a77b69052e-50d62b4fcfdmr297651321cf.8.1775649410994; \n\tWed, 08 Apr 2026 04:56:50 -0700 (PDT)"],"From":"Hans de Goede <johannes.goede@oss.qualcomm.com>","To":"libcamera-devel@lists.libcamera.org","Cc":"Loic Poulain <loic.poulain@oss.qualcomm.com>,\n\tHans de Goede <johannes.goede@oss.qualcomm.com>","Subject":"[RFC 1/3] camss: Add CAMSS pipeline handler","Date":"Wed,  8 Apr 2026 13:56:43 +0200","Message-ID":"<20260408115645.12487-2-johannes.goede@oss.qualcomm.com>","X-Mailer":"git-send-email 2.53.0","In-Reply-To":"<20260408115645.12487-1-johannes.goede@oss.qualcomm.com>","References":"<20260408115645.12487-1-johannes.goede@oss.qualcomm.com>","MIME-Version":"1.0","Content-Transfer-Encoding":"8bit","X-Proofpoint-Spam-Details-Enc":"AW1haW4tMjYwNDA4MDExMCBTYWx0ZWRfXxA0h3CjJJoC9\n\tQhDeunPwNO0U8B16SiSfX1WcAWVkQRjJJy7XEDx1ZZ2XC6rWnKJiILDBG6Ao6sNx29phiBU3XtX\n\trRnINa1O8g9qLec6M5ahF/UzEjeHYmxbazLhtAGPoOlj/bflQOrS1zqfLCeaoM39WvLeXbLq2hB\n\tRnILqXxzpTHXQOeUhBM+1cW8yGT42Vrl0mJYa6rlEgKAYYiEB3W9eNovp9og/h9JY/AnFxjT5/6\n\triG0Sqcna2MoLIuYrbDRAwPcGTLG4UNdE3PssevYNHuh6ItkDeIoYknIGTSMOU6c2GYujbvqG47\n\tsvaDIralhlv/YYY4m7hAH6FUBfKG96HJhWDfolZ3kgr8yG81qHV7fnhSjsAfGjMOujBWZf/YtYM\n\tUidVX/A7BJ/MFkF4tCLJYwARIKzpFx26DSoPLdBwufnFgLA/GZCDumhZIaLulMgdtWRkhyRFkyQ\n\tCGcqp4r/BjcRHhXu/TA==","X-Proofpoint-GUID":"fKS4-O6YsYYdXg-hDGsLe-gciM7XlC0H","X-Proofpoint-ORIG-GUID":"fKS4-O6YsYYdXg-hDGsLe-gciM7XlC0H","X-Authority-Analysis":"v=2.4 cv=K4AS2SWI c=1 sm=1 tr=0 ts=69d64286 cx=c_pps\n\ta=JbAStetqSzwMeJznSMzCyw==:117 a=xqWC_Br6kY4A:10 a=A5OVakUREuEA:10\n\ta=s4-Qcg_JpJYA:10 a=VkNPw1HP01LnGYTKEx00:22 a=u7WPNUs3qKkmUXheDGA7:22\n\ta=gowsoOTTUOVcmtlkKump:22 a=EUspDBNiAAAA:8 a=Bki1rqwwrQ7S0vOqMWMA:9\n\ta=HcG5QfLkgl6EuUUG:21 a=uxP6HrT_eTzRwkO_Te1X:22","X-Proofpoint-Virus-Version":"vendor=baseguard\n\tengine=ICAP:2.0.293, Aquarius:18.0.1143, Hydra:6.1.51,\n\tFMLib:17.12.100.49\n\tdefinitions=2026-04-08_03,2026-04-08_01,2025-10-01_01","X-Proofpoint-Spam-Details":"rule=outbound_notspam policy=outbound score=0\n\tpriorityscore=1501 impostorscore=0 lowpriorityscore=0 clxscore=1015\n\tphishscore=0 malwarescore=0 spamscore=0 bulkscore=0 adultscore=0\n\tsuspectscore=0 classifier=typeunknown authscore=0 authtc= authcc=\n\troute=outbound adjust=0 reason=mlx scancount=1\n\tengine=8.22.0-2604010000\n\tdefinitions=main-2604080110","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 CAMSS pipeline handler. This initial version basically replaces\nthe simple pipeline handler camss support and still depends on\nthe software ISP.\n\nThis uses a CamssIsp virtual base class which can be used to later\nimplement hardware ISP support without requiring invasive changes to\nthe initial pipeline handler introduced here.\n\nSupport for the Offline Processing Engine HW ISP found in the Qualcomm\nAgetti SoC is introduced in a later patch in this series.\n\nSince the OPE is an offline ISP, the CAMSS pipeline handler is loosely\nbased on the existing IPU3 pipeline handler as that also is for an\noffline ISP.\n\nThe CamssFrameInfo class is an almost 1:1 copy of the IPU3 code and is\na candidate for later being factored out into a generic helper class which\ncould be shared between the IPU3, CAMSS and simple pipeline handlers.\n\nSigned-off-by: Hans de Goede <johannes.goede@oss.qualcomm.com>\n---\n meson.build                                   |   1 +\n meson_options.txt                             |   1 +\n src/ipa/meson.build                           |   1 +\n src/libcamera/pipeline/camss/camss.cpp        | 703 ++++++++++++++++++\n src/libcamera/pipeline/camss/camss_csi.cpp    | 541 ++++++++++++++\n src/libcamera/pipeline/camss/camss_csi.h      | 125 ++++\n src/libcamera/pipeline/camss/camss_frames.cpp | 106 +++\n src/libcamera/pipeline/camss/camss_frames.h   |  59 ++\n src/libcamera/pipeline/camss/camss_isp.cpp    |  26 +\n src/libcamera/pipeline/camss/camss_isp.h      |  59 ++\n .../pipeline/camss/camss_isp_soft.cpp         | 203 +++++\n src/libcamera/pipeline/camss/camss_isp_soft.h |  50 ++\n src/libcamera/pipeline/camss/meson.build      |   9 +\n src/libcamera/pipeline/simple/simple.cpp      |   2 +-\n 14 files changed, 1885 insertions(+), 1 deletion(-)\n create mode 100644 src/libcamera/pipeline/camss/camss.cpp\n create mode 100644 src/libcamera/pipeline/camss/camss_csi.cpp\n create mode 100644 src/libcamera/pipeline/camss/camss_csi.h\n create mode 100644 src/libcamera/pipeline/camss/camss_frames.cpp\n create mode 100644 src/libcamera/pipeline/camss/camss_frames.h\n create mode 100644 src/libcamera/pipeline/camss/camss_isp.cpp\n create mode 100644 src/libcamera/pipeline/camss/camss_isp.h\n create mode 100644 src/libcamera/pipeline/camss/camss_isp_soft.cpp\n create mode 100644 src/libcamera/pipeline/camss/camss_isp_soft.h\n create mode 100644 src/libcamera/pipeline/camss/meson.build","diff":"diff --git a/meson.build b/meson.build\nindex 2e2a27ef4..15cf8f6a3 100644\n--- a/meson.build\n+++ b/meson.build\n@@ -216,6 +216,7 @@ wanted_pipelines = get_option('pipelines')\n arch_arm = ['arm', 'aarch64']\n arch_x86 = ['x86', 'x86_64']\n pipelines_support = {\n+    'camss':        arch_arm,\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 20baacc4f..0d793a356 100644\n--- a/meson_options.txt\n+++ b/meson_options.txt\n@@ -78,6 +78,7 @@ option('pipelines',\n         choices : [\n             'all',\n             'auto',\n+            'camss',\n             'imx8-isi',\n             'ipu3',\n             'mali-c55',\ndiff --git a/src/ipa/meson.build b/src/ipa/meson.build\nindex c583c7efd..a1f1a5200 100644\n--- a/src/ipa/meson.build\n+++ b/src/ipa/meson.build\n@@ -25,6 +25,7 @@ subdir('libipa')\n ipa_sign = files('ipa-sign.sh')\n \n supported_ipas = {\n+    'camss':      'simple',\n     'ipu3':       'ipu3',\n     'mali-c55':   'mali-c55',\n     'rkisp1':     'rkisp1',\ndiff --git a/src/libcamera/pipeline/camss/camss.cpp b/src/libcamera/pipeline/camss/camss.cpp\nnew file mode 100644\nindex 000000000..6939ac115\n--- /dev/null\n+++ b/src/libcamera/pipeline/camss/camss.cpp\n@@ -0,0 +1,703 @@\n+/* SPDX-License-Identifier: LGPL-2.1-or-later */\n+/*\n+ * Pipeline handler for Qualcomm CAMSS\n+ *\n+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.\n+ *\n+ * Partially based on other pipeline-handlers 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 <memory>\n+#include <queue>\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/formats.h>\n+#include <libcamera/property_ids.h>\n+#include <libcamera/stream.h>\n+\n+#include \"libcamera/internal/camera.h\"\n+#include \"libcamera/internal/camera_sensor.h\"\n+#include \"libcamera/internal/camera_sensor_properties.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/request.h\"\n+\n+#include \"camss_csi.h\"\n+#include \"camss_frames.h\"\n+#include \"camss_isp.h\"\n+#include \"camss_isp_soft.h\"\n+\n+namespace libcamera {\n+\n+LOG_DEFINE_CATEGORY(Camss)\n+\n+class CamssCameraData : public Camera::Private\n+{\n+public:\n+\tCamssCameraData(PipelineHandler *pipe)\n+\t\t: Camera::Private(pipe)\n+\t{\n+\t}\n+\n+\tvoid csiBufferReady(FrameBuffer *buffer);\n+\tvoid ispOutputBufferReady(FrameBuffer *buffer);\n+\tvoid frameStart(uint32_t sequence);\n+\tvoid statsReady(uint32_t frame, uint32_t bufferId);\n+\tvoid metadataReady(unsigned int id, const ControlList &metadata);\n+\tvoid setSensorControls(const ControlList &sensorControls);\n+\n+\tvoid queuePendingRequests();\n+\tvoid cancelPendingRequests();\n+\n+\tstd::unique_ptr<CamssCsiCamera> csi_;\n+\tstd::unique_ptr<CamssIsp> isp_;\n+\tstd::unique_ptr<DelayedControls> delayedCtrls_;\n+\tCamssFrames frameInfos_;\n+\n+\t/* Requests for which no buffer has been queued to the CSI receiver yet. */\n+\tstd::queue<Request *> pendingRequests_;\n+};\n+\n+class CamssCameraConfiguration : public CameraConfiguration\n+{\n+public:\n+\tstatic constexpr unsigned int kBufferCount = 4;\n+\tstatic constexpr unsigned int kMaxStreams = 2;\n+\n+\tCamssCameraConfiguration(CamssCameraData *data);\n+\tStatus validate() override;\n+\n+\t/* Cache the combinedTransform_ that will be applied to the sensor */\n+\tTransform combinedTransform_;\n+\tStreamConfiguration csiConfig_;\n+\tStreamConfiguration ispConfig_;\n+\n+private:\n+\t/*\n+\t * The CamssCameraData instance is guaranteed to be valid as long as the\n+\t * corresponding Camera instance is valid. In order to borrow a\n+\t * reference to the camera data, store a new reference to the camera.\n+\t */\n+\tconst CamssCameraData *data_;\n+};\n+\n+class PipelineHandlerCamss : public PipelineHandler\n+{\n+public:\n+\tPipelineHandlerCamss(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+\tbool match(DeviceEnumerator *enumerator) override;\n+\n+private:\n+\tCamssCameraData *cameraData(Camera *camera)\n+\t{\n+\t\treturn static_cast<CamssCameraData *>(camera->_d());\n+\t}\n+\n+\tint allocateBuffers(Camera *camera);\n+\tvoid freeBuffers(Camera *camera);\n+\tstatic int validateConfigMatchesV4L2DeviceFormat(const StreamConfiguration &cfg,\n+\t\t\t\t\t\t\t const V4L2DeviceFormat &fmt);\n+\n+\tCamssCsi csi_;\n+};\n+\n+CamssCameraConfiguration::CamssCameraConfiguration(CamssCameraData *data)\n+\t: CameraConfiguration()\n+{\n+\tdata_ = data;\n+}\n+\n+CameraConfiguration::Status CamssCameraConfiguration::validate()\n+{\n+\tStatus status = Valid;\n+\n+\tif (config_.empty())\n+\t\treturn Invalid;\n+\n+\t/*\n+\t * Validate the requested transform against the sensor capabilities and\n+\t * rotation and store the final combined transform that configure() will\n+\t * need to apply to the sensor to save us working it out again.\n+\t */\n+\tOrientation requestedOrientation = orientation;\n+\tcombinedTransform_ = data_->csi_->sensor()->computeTransform(&orientation);\n+\tif (orientation != requestedOrientation)\n+\t\tstatus = Adjusted;\n+\n+\t/* Max. 1 RAW + 1 processed stream is supported (for now). */\n+\tStreamConfiguration rawConfig;\n+\tStreamConfiguration processedConfig;\n+\tunsigned int rawCount = 0;\n+\tunsigned int processedCount = 0;\n+\n+\tfor (const StreamConfiguration &cfg : config_) {\n+\t\tconst PixelFormatInfo &info = PixelFormatInfo::info(cfg.pixelFormat);\n+\n+\t\tif (info.colourEncoding == PixelFormatInfo::ColourEncodingRAW) {\n+\t\t\tif (rawCount) {\n+\t\t\t\tLOG(Camss, Debug) << \"Multiple raw streams not supported\";\n+\t\t\t\treturn Invalid;\n+\t\t\t}\n+\t\t\trawConfig = cfg;\n+\t\t\trawCount++;\n+\t\t} else {\n+\t\t\tif (processedCount) {\n+\t\t\t\tLOG(Camss, Debug) << \"Multiple processed streams not supported\";\n+\t\t\t\treturn Invalid;\n+\t\t\t}\n+\t\t\tprocessedConfig = cfg;\n+\t\t\tprocessedCount++;\n+\t\t}\n+\t}\n+\n+\tif (!processedCount) {\n+\t\t/*\n+\t\t * \\todo allow this, add dummyISP ISP class which only\n+\t\t * calls CPU stats on ready raw output buffers + runs the result\n+\t\t * through the softIPA to get sensor-control + metadata-info\n+\t\t */\n+\t\tLOG(Camss, Debug)\n+\t\t\t<< \"Camera configuration cannot support raw-only streams\";\n+\t\treturn Invalid;\n+\t}\n+\n+\tif (!rawCount) {\n+\t\trawConfig.size = processedConfig.size;\n+\t\trawConfig.bufferCount = processedConfig.bufferCount;\n+\t}\n+\n+\tcsiConfig_ = data_->csi_->validate(rawConfig);\n+\tif (!csiConfig_.pixelFormat.isValid())\n+\t\treturn Invalid;\n+\n+\tLOG(Camss, Debug) << \"CSI configuration: \" << csiConfig_.toString()\n+\t\t\t  << \" stride \" << csiConfig_.stride\n+\t\t\t  << \" frameSize \" << csiConfig_.frameSize;\n+\n+\tispConfig_ = data_->isp_->validate(csiConfig_, processedConfig);\n+\tif (!ispConfig_.pixelFormat.isValid())\n+\t\treturn Invalid;\n+\n+\tLOG(Camss, Debug) << \"ISP configuration: \" << ispConfig_.toString()\n+\t\t\t  << \" stride \" << ispConfig_.stride\n+\t\t\t  << \" frameSize \" << ispConfig_.frameSize;\n+\n+\tfor (unsigned int i = 0; i < config_.size(); ++i) {\n+\t\tconst PixelFormatInfo &info = PixelFormatInfo::info(config_[i].pixelFormat);\n+\t\tconst StreamConfiguration *hwCfg, originalCfg = config_[i];\n+\t\tStream *stream;\n+\n+\t\tLOG(Camss, Debug) << \"Validating stream: \" << config_[i].toString();\n+\n+\t\tif (info.colourEncoding == PixelFormatInfo::ColourEncodingRAW) {\n+\t\t\thwCfg = &csiConfig_;\n+\t\t\tstream = &data_->csi_->rawStream_;\n+\t\t} else {\n+\t\t\thwCfg = &ispConfig_;\n+\t\t\tstream = &data_->isp_->outStream_;\n+\t\t}\n+\n+\t\tStreamConfiguration &cfg = config_[i];\n+\t\tcfg.size = hwCfg->size;\n+\t\tcfg.pixelFormat = hwCfg->pixelFormat;\n+\t\tcfg.stride = hwCfg->stride;\n+\t\tcfg.frameSize = hwCfg->frameSize;\n+\t\tcfg.bufferCount = hwCfg->bufferCount;\n+\t\tcfg.setStream(stream);\n+\n+\t\tif (cfg.pixelFormat != originalCfg.pixelFormat ||\n+\t\t    cfg.size != originalCfg.size) {\n+\t\t\tLOG(Camss, Debug)\n+\t\t\t\t<< \"Stream \" << i << \" configuration adjusted to \"\n+\t\t\t\t<< cfg.toString();\n+\t\t\tstatus = Adjusted;\n+\t\t}\n+\n+\t\tif (originalCfg.bufferCount && cfg.bufferCount != originalCfg.bufferCount) {\n+\t\t\tLOG(Camss, Debug)\n+\t\t\t\t<< \"Adjusting bufferCount from \" << originalCfg.bufferCount\n+\t\t\t\t<< \" to \" << cfg.bufferCount;\n+\t\t\tstatus = Adjusted;\n+\t\t}\n+\n+\t\t/*\n+\t\t * \\todo copy-pasted from src/libcamera/pipeline/simple/simple.cpp turn\n+\t\t * this into a generic helper?\n+\t\t * Best effort to fix the color space. If the color space is not set,\n+\t\t * set it according to the pixel format, which may not be correct (pixel\n+\t\t * formats and color spaces are different things, although somewhat\n+\t\t * related) but we don't have a better option at the moment. Then in any\n+\t\t * case, perform the standard pixel format based color space adjustment.\n+\t\t */\n+\t\tif (!cfg.colorSpace) {\n+\t\t\tconst PixelFormatInfo &pfi = PixelFormatInfo::info(cfg.pixelFormat);\n+\t\t\tswitch (pfi.colourEncoding) {\n+\t\t\tcase PixelFormatInfo::ColourEncodingRGB:\n+\t\t\t\tcfg.colorSpace = ColorSpace::Srgb;\n+\t\t\t\tbreak;\n+\t\t\tcase PixelFormatInfo::ColourEncodingYUV:\n+\t\t\t\tcfg.colorSpace = ColorSpace::Sycc;\n+\t\t\t\tbreak;\n+\t\t\tdefault:\n+\t\t\t\tcfg.colorSpace = ColorSpace::Raw;\n+\t\t\t}\n+\t\t\t/*\n+\t\t\t * Adjust the assigned color space to make sure everything is OK.\n+\t\t\t * Since this is assigning an unspecified color space rather than\n+\t\t\t * adjusting a requested one, changes here shouldn't set the status\n+\t\t\t * to Adjusted.\n+\t\t\t */\n+\t\t\tcfg.colorSpace->adjust(cfg.pixelFormat);\n+\t\t\tLOG(Camss, Debug)\n+\t\t\t\t<< \"Unspecified color space set to \"\n+\t\t\t\t<< cfg.colorSpace.value().toString();\n+\t\t} else {\n+\t\t\tif (cfg.colorSpace->adjust(cfg.pixelFormat)) {\n+\t\t\t\tLOG(Camss, Debug)\n+\t\t\t\t\t<< \"Color space adjusted to \"\n+\t\t\t\t\t<< cfg.colorSpace.value().toString();\n+\t\t\t\tstatus = Adjusted;\n+\t\t\t}\n+\t\t}\n+\t}\n+\n+\treturn status;\n+}\n+\n+PipelineHandlerCamss::PipelineHandlerCamss(CameraManager *manager)\n+\t: PipelineHandler(manager), csi_()\n+{\n+}\n+\n+std::unique_ptr<CameraConfiguration>\n+PipelineHandlerCamss::generateConfiguration(Camera *camera, Span<const StreamRole> roles)\n+{\n+\tCamssCameraData *data = cameraData(camera);\n+\tstd::unique_ptr<CamssCameraConfiguration> config =\n+\t\tstd::make_unique<CamssCameraConfiguration>(data);\n+\tStreamConfiguration cfg, csiConfig;\n+\n+\tif (roles.empty())\n+\t\treturn config;\n+\n+\tcsiConfig = data->csi_->generateConfiguration();\n+\tif (!csiConfig.pixelFormat.isValid())\n+\t\treturn nullptr;\n+\n+\tLOG(Camss, Debug) << \"Generated CSI cfg \" << csiConfig;\n+\n+\tbool processedRequested = false;\n+\tbool rawRequested = false;\n+\tfor (const auto &role : roles) {\n+\t\tif (role == StreamRole::Raw)\n+\t\t\trawRequested = true;\n+\t\telse\n+\t\t\tprocessedRequested = true;\n+\t}\n+\n+\tif (rawRequested)\n+\t\tconfig->addConfiguration(csiConfig);\n+\n+\tif (processedRequested) {\n+\t\tcfg = data->isp_->generateConfiguration(csiConfig);\n+\t\tif (!cfg.pixelFormat.isValid())\n+\t\t\treturn nullptr;\n+\n+\t\tLOG(Camss, Debug) << \"Generated ISP cfg \" << cfg;\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 PipelineHandlerCamss::validateConfigMatchesV4L2DeviceFormat(const StreamConfiguration &cfg,\n+\t\t\t\t\t\t\t\tconst V4L2DeviceFormat &fmt)\n+{\n+\tif (cfg.pixelFormat != fmt.fourcc.toPixelFormat(false) || cfg.size != fmt.size ||\n+\t    cfg.stride != fmt.planes[0].bpl || cfg.frameSize != fmt.planes[0].size) {\n+\t\tLOG(Camss, Error)\n+\t\t\t<< \"configure() StreamConfiguration vs V4L2DeviceFormat mismatch\"\n+\t\t\t<< \" pixelFormat \" << cfg.pixelFormat << \", \" << fmt.fourcc.toPixelFormat(false)\n+\t\t\t<< \" size \" << cfg.size << \", \" << fmt.size\n+\t\t\t<< \" stride \" << cfg.stride << \", \" << fmt.planes[0].bpl\n+\t\t\t<< \" frameSize \" << cfg.frameSize << \", \" << fmt.planes[0].size;\n+\t\treturn -EIO;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+int PipelineHandlerCamss::configure(Camera *camera, CameraConfiguration *c)\n+{\n+\tCamssCameraConfiguration *config =\n+\t\tstatic_cast<CamssCameraConfiguration *>(c);\n+\tCamssCameraData *data = cameraData(camera);\n+\tV4L2DeviceFormat csiFormat;\n+\tint ret;\n+\n+\tret = data->csi_->configure(config->csiConfig_, config->combinedTransform_, &csiFormat);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tret = validateConfigMatchesV4L2DeviceFormat(config->csiConfig_, csiFormat);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\treturn data->isp_->configure(config->csiConfig_, config->ispConfig_);\n+}\n+\n+int PipelineHandlerCamss::exportFrameBuffers(Camera *camera, Stream *stream,\n+\t\t\t\t\t     std::vector<std::unique_ptr<FrameBuffer>> *buffers)\n+{\n+\tCamssCameraData *data = cameraData(camera);\n+\tunsigned int count = stream->configuration().bufferCount;\n+\n+\tif (stream == &data->csi_->rawStream_)\n+\t\treturn data->csi_->exportBuffers(count, buffers);\n+\telse if (stream == &data->isp_->outStream_)\n+\t\treturn data->isp_->exportOutputBuffers(stream, count, buffers);\n+\n+\treturn -EINVAL;\n+}\n+\n+int PipelineHandlerCamss::allocateBuffers(Camera *camera)\n+{\n+\tCamssCameraData *data = cameraData(camera);\n+\tunsigned int bufferCount;\n+\tint ret;\n+\n+\tbufferCount = std::max({\n+\t\tdata->csi_->rawStream_.configuration().bufferCount,\n+\t\tdata->isp_->outStream_.configuration().bufferCount,\n+\t});\n+\n+\tret = data->isp_->allocateBuffers(bufferCount);\n+\tif (ret < 0)\n+\t\treturn ret;\n+\n+\tdata->frameInfos_.init();\n+\tdata->frameInfos_.bufferAvailable.connect(\n+\t\tdata, &CamssCameraData::queuePendingRequests);\n+\n+\treturn 0;\n+}\n+\n+void PipelineHandlerCamss::freeBuffers(Camera *camera)\n+{\n+\tCamssCameraData *data = cameraData(camera);\n+\n+\tdata->frameInfos_.clear();\n+\tdata->isp_->freeBuffers();\n+}\n+\n+int PipelineHandlerCamss::start(Camera *camera, [[maybe_unused]] const ControlList *controls)\n+{\n+\tCamssCameraData *data = cameraData(camera);\n+\tint ret;\n+\n+\t/* Allocate buffers for internal pipeline usage. */\n+\tret = allocateBuffers(camera);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tdata->delayedCtrls_->reset();\n+\n+\tret = data->csi_->start();\n+\tif (ret)\n+\t\tgoto freebuffers;\n+\n+\tret = data->isp_->start();\n+\tif (ret)\n+\t\tgoto stop;\n+\n+\treturn 0;\n+\n+stop:\n+\tdata->csi_->stop();\n+freebuffers:\n+\tfreeBuffers(camera);\n+\n+\tLOG(Camss, Error) << \"Failed to start camera \" << camera->id();\n+\treturn ret;\n+}\n+\n+void PipelineHandlerCamss::stopDevice(Camera *camera)\n+{\n+\tCamssCameraData *data = cameraData(camera);\n+\n+\tdata->cancelPendingRequests();\n+\n+\tdata->isp_->stop();\n+\tdata->csi_->stop();\n+\n+\tfreeBuffers(camera);\n+}\n+\n+void CamssCameraData::cancelPendingRequests()\n+{\n+\twhile (!pendingRequests_.empty()) {\n+\t\tRequest *request = pendingRequests_.front();\n+\n+\t\tfor (const auto &[stream, buffer] : request->buffers()) {\n+\t\t\tbuffer->_d()->cancel();\n+\t\t\tpipe()->completeBuffer(request, buffer);\n+\t\t}\n+\n+\t\tpipe()->completeRequest(request);\n+\t\tpendingRequests_.pop();\n+\t}\n+}\n+\n+void CamssCameraData::queuePendingRequests()\n+{\n+\twhile (!pendingRequests_.empty()) {\n+\t\tRequest *request = pendingRequests_.front();\n+\n+\t\tCamssFrames::Info *info = frameInfos_.create(request);\n+\t\tif (!info)\n+\t\t\tbreak;\n+\n+\t\t/*\n+\t\t * Queue a buffer on the CSI, using the raw stream buffer\n+\t\t * provided in the request, if any, or a CIO2 internal buffer\n+\t\t * otherwise.\n+\t\t */\n+\t\tFrameBuffer *reqRawBuffer = request->findBuffer(&csi_->rawStream_);\n+\t\tFrameBuffer *rawBuffer = csi_->queueBuffer(request, reqRawBuffer);\n+\t\t/*\n+\t\t * \\todo If queueBuffer fails in queuing a buffer to the device,\n+\t\t * report the request as error by cancelling the request and\n+\t\t * calling PipelineHandler::completeRequest().\n+\t\t */\n+\t\tif (!rawBuffer) {\n+\t\t\tframeInfos_.remove(info);\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\tinfo->rawBuffer = rawBuffer;\n+\n+\t\tpendingRequests_.pop();\n+\t}\n+}\n+\n+int PipelineHandlerCamss::queueRequestDevice(Camera *camera, Request *request)\n+{\n+\tCamssCameraData *data = cameraData(camera);\n+\n+\tdata->pendingRequests_.push(request);\n+\tdata->queuePendingRequests();\n+\n+\treturn 0;\n+}\n+\n+bool PipelineHandlerCamss::match(DeviceEnumerator *enumerator)\n+{\n+\tCamssCsi::Cameras csiCams;\n+\n+\tcsiCams = csi_.match(this, enumerator);\n+\tif (csiCams.empty())\n+\t\treturn false;\n+\n+\tunsigned int numCameras = 0;\n+\tfor (unsigned int i = 0; i < csiCams.size(); i++) {\n+\t\tstd::unique_ptr<CamssCameraData> data =\n+\t\t\tstd::make_unique<CamssCameraData>(this);\n+\t\tdata->csi_ = std::move(csiCams[i]);\n+\n+\t\tdata->csi_->frameStart().connect(data.get(),\n+\t\t\t\t\t\t &CamssCameraData::frameStart);\n+\t\tdata->csi_->bufferReady().connect(data.get(),\n+\t\t\t\t\t\t  &CamssCameraData::csiBufferReady);\n+\t\tdata->csi_->bufferAvailable.connect(data.get(),\n+\t\t\t\t\t\t    &CamssCameraData::queuePendingRequests);\n+\n+\t\tCameraSensor *sensor = data->csi_->sensor();\n+\n+\t\t/* Initialize the camera properties. */\n+\t\tdata->properties_ = sensor->properties();\n+\n+\t\tconst CameraSensorProperties::SensorDelays &delays = sensor->sensorDelays();\n+\t\tstd::unordered_map<uint32_t, DelayedControls::ControlParams> params = {\n+\t\t\t{ V4L2_CID_ANALOGUE_GAIN, { delays.gainDelay, false } },\n+\t\t\t{ V4L2_CID_EXPOSURE, { delays.exposureDelay, false } },\n+\t\t};\n+\n+\t\tdata->delayedCtrls_ =\n+\t\t\tstd::make_unique<DelayedControls>(sensor->device(), params);\n+\n+\t\tdata->isp_ = std::make_unique<CamssIspSoft>(this, sensor, &data->controlInfo_);\n+\t\tif (!data->isp_->isValid()) {\n+\t\t\tLOG(Camss, Error) << \"Failed to create software ISP\";\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tdata->isp_->inputBufferReady.connect(data->csi_.get(),\n+\t\t\t\t\t\t     &CamssCsiCamera::tryReturnBuffer);\n+\t\tdata->isp_->outputBufferReady.connect(data.get(),\n+\t\t\t\t\t\t      &CamssCameraData::ispOutputBufferReady);\n+\t\tdata->isp_->statsReady.connect(data.get(), &CamssCameraData::statsReady);\n+\t\tdata->isp_->metadataReady.connect(data.get(), &CamssCameraData::metadataReady);\n+\t\tdata->isp_->setSensorControls.connect(data.get(), &CamssCameraData::setSensorControls);\n+\n+\t\t/* Create and register the Camera instance. */\n+\t\tstd::set<Stream *> streams = {\n+\t\t\t&data->isp_->outStream_,\n+\t\t\t&data->csi_->rawStream_,\n+\t\t};\n+\t\tstd::shared_ptr<Camera> camera =\n+\t\t\tCamera::create(std::move(data), sensor->id(), streams);\n+\n+\t\tregisterCamera(std::move(camera));\n+\t\tnumCameras++;\n+\t}\n+\n+\treturn numCameras != 0;\n+}\n+\n+void CamssCameraData::setSensorControls(const ControlList &sensorControls)\n+{\n+\tdelayedCtrls_->push(sensorControls);\n+\n+\t/*\n+\t * Directly apply controls now if there is no frameStart signal.\n+\t *\n+\t * \\todo Applying controls directly not only increases the risk of\n+\t * applying them to the wrong frame (or across a frame boundary),\n+\t * but it also bypasses delayedCtrls_, creating AGC regulation issues.\n+\t * Both problems should be fixed.\n+\t */\n+\tif (!csi_->supportsFrameStart()) {\n+\t\tControlList ctrls(sensorControls);\n+\t\tcsi_->sensor()->setControls(&ctrls);\n+\t}\n+}\n+\n+void CamssCameraData::metadataReady(unsigned int id, const ControlList &metadata)\n+{\n+\tCamssFrames::Info *info = frameInfos_.find(id);\n+\tif (!info)\n+\t\treturn;\n+\n+\tRequest *request = info->request;\n+\trequest->_d()->metadata().merge(metadata);\n+\n+\tinfo->metadataProcessed = true;\n+\tif (frameInfos_.tryComplete(info))\n+\t\tpipe()->completeRequest(request);\n+}\n+\n+/* -----------------------------------------------------------------------------\n+ * Buffer Ready slots\n+ */\n+\n+/**\n+ * \\brief Handle buffers completion at the ISP output\n+ * \\param[in] buffer The completed buffer\n+ *\n+ * Buffers completed from the ISP output are directed to the application.\n+ */\n+void CamssCameraData::ispOutputBufferReady(FrameBuffer *buffer)\n+{\n+\tCamssFrames::Info *info = frameInfos_.find(buffer);\n+\tif (!info)\n+\t\treturn;\n+\n+\tRequest *request = info->request;\n+\n+\tpipe()->completeBuffer(request, buffer);\n+\tif (frameInfos_.tryComplete(info))\n+\t\tpipe()->completeRequest(request);\n+}\n+\n+/**\n+ * \\brief Handle buffers completion at the CSI-receiver output\n+ * \\param[in] buffer The completed buffer\n+ *\n+ * Buffers completed from the CSI-receiver are immediately queued to the ISP\n+ * for further processing.\n+ */\n+void CamssCameraData::csiBufferReady(FrameBuffer *buffer)\n+{\n+\tCamssFrames::Info *info = frameInfos_.find(buffer);\n+\tif (!info)\n+\t\treturn;\n+\n+\tRequest *request = info->request;\n+\n+\t/* If the buffer is cancelled force a complete of the whole request. */\n+\tif (buffer->metadata().status == FrameMetadata::FrameCancelled) {\n+\t\tfor (const auto &[stream, b] : request->buffers()) {\n+\t\t\tb->_d()->cancel();\n+\t\t\tpipe()->completeBuffer(request, b);\n+\t\t}\n+\n+\t\tframeInfos_.remove(info);\n+\t\tpipe()->completeRequest(request);\n+\t\treturn;\n+\t}\n+\n+\t/* \\todo what about metadata().status == FrameMetadata::FrameError ? */\n+\n+\t/*\n+\t * Record the sensor's timestamp in the request metadata.\n+\t *\n+\t * \\todo The sensor timestamp should be better estimated by connecting\n+\t * to the V4L2Device::frameStart signal.\n+\t */\n+\trequest->_d()->metadata().set(controls::SensorTimestamp,\n+\t\t\t\t      buffer->metadata().timestamp);\n+\n+\tinfo->effectiveSensorControls = delayedCtrls_->get(buffer->metadata().sequence);\n+\n+\tif (request->findBuffer(&csi_->rawStream_))\n+\t\tpipe()->completeBuffer(request, buffer);\n+\n+\tisp_->queueBuffers(request, buffer);\n+}\n+\n+/*\n+ * \\brief Handle the start of frame exposure signal\n+ * \\param[in] sequence The sequence number of frame\n+ */\n+void CamssCameraData::frameStart(uint32_t sequence)\n+{\n+\tdelayedCtrls_->applyControls(sequence);\n+}\n+\n+void CamssCameraData::statsReady(uint32_t frame, uint32_t bufferId)\n+{\n+\tisp_->processStats(frame, bufferId, delayedCtrls_->get(frame));\n+}\n+\n+REGISTER_PIPELINE_HANDLER(PipelineHandlerCamss, \"camss\")\n+\n+} /* namespace libcamera */\ndiff --git a/src/libcamera/pipeline/camss/camss_csi.cpp b/src/libcamera/pipeline/camss/camss_csi.cpp\nnew file mode 100644\nindex 000000000..b7191a8e9\n--- /dev/null\n+++ b/src/libcamera/pipeline/camss/camss_csi.cpp\n@@ -0,0 +1,541 @@\n+/* SPDX-License-Identifier: LGPL-2.1-or-later */\n+/*\n+ * Qualcomm CAMSS CSI phy/decoder and VFE handling\n+ *\n+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.\n+ *\n+ * Partially based on other pipeline-handlers which are:\n+ * Copyright (C) 2020, Laurent Pinchart\n+ * Copyright (C) 2019, Martijn Braam\n+ * Copyright (C) 2019, Google Inc.\n+ */\n+\n+#include \"camss_csi.h\"\n+\n+#include <cmath>\n+#include <limits>\n+\n+#include <linux/media-bus-format.h>\n+\n+#include <libcamera/formats.h>\n+#include <libcamera/geometry.h>\n+#include <libcamera/stream.h>\n+#include <libcamera/transform.h>\n+\n+#include \"libcamera/internal/bayer_format.h\"\n+#include \"libcamera/internal/camera_sensor.h\"\n+#include \"libcamera/internal/framebuffer.h\"\n+#include \"libcamera/internal/media_device.h\"\n+#include \"libcamera/internal/v4l2_subdevice.h\"\n+\n+namespace libcamera {\n+\n+LOG_DECLARE_CATEGORY(Camss)\n+\n+CamssCsiCamera::CamssCsiCamera()\n+{\n+}\n+\n+/**\n+ * \\brief Get output V4L2PixelFormat for media bus code\n+ *\n+ * Get output video node V4L2PixelFormat for the given media bus code.\n+ * \\param[in] code The media bus code\n+ *\n+ * \\return V4L2PixelFormat\n+ */\n+V4L2PixelFormat CamssCsiCamera::mbusCodeToV4L2PixelFormat(unsigned int code) const\n+{\n+\tV4L2VideoDevice::Formats formats = output_->formats(code);\n+\n+\tif (formats.empty()) {\n+\t\tLOG(Camss, Error)\n+\t\t\t<< \"No formats for media bus code \" << code;\n+\t\treturn V4L2PixelFormat();\n+\t}\n+\n+\t/*\n+\t * camss supports only 1 V4L2 output format per media bus code and not\n+\t * multiple (e.g. not mipi-packed + sparse for raw-bayer).\n+\t */\n+\treturn formats.begin()->first;\n+}\n+\n+/**\n+ * \\brief Get output PixelFormat for media bus code\n+ *\n+ * Get output video node PixelFormat for the given media bus code.\n+ * \\param[in] code The media bus code\n+ *\n+ * \\return PixelFormat\n+ */\n+PixelFormat CamssCsiCamera::mbusCodeToPixelFormat(unsigned int code) const\n+{\n+\tV4L2PixelFormat v4l2Format = mbusCodeToV4L2PixelFormat(code);\n+\tif (!v4l2Format.isValid())\n+\t\treturn PixelFormat();\n+\n+\treturn v4l2Format.toPixelFormat();\n+}\n+\n+/**\n+ * \\brief Get media bus code for desired output PixelFormat\n+ *\n+ * Get the media bus code for a desired output video node PixelFormat.\n+ * \\param[in] format The PixelFormat\n+ *\n+ * \\return Media bus code or 0 if no matching code is found\n+ */\n+unsigned int CamssCsiCamera::PixelFormatToMbusCode(const PixelFormat &format) const\n+{\n+\tfor (unsigned int code : sensor_->mbusCodes()) {\n+\t\tPixelFormat pixelFormat = mbusCodeToPixelFormat(code);\n+\t\tif (pixelFormat == format)\n+\t\t\treturn code;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+/**\n+ * \\brief Retrieve the best sensor format for a desired output size and format\n+ * \\param[in] size The desired size\n+ * \\param[in] format The desired PixelFormat\n+ *\n+ * \\a size indicates the desired size at the output of the sensor. This method\n+ * selects the best media bus code and size supported by the sensor according\n+ * to the following criteria.\n+ *\n+ * - The desired \\a size shall fit in the sensor output size to avoid the need\n+ *   to up-scale.\n+ * - The aspect ratio of sensor output size shall be as close as possible to\n+ *   the sensor's native resolution field of view.\n+ * - The sensor output size shall be as small as possible to lower the required\n+ *   bandwidth.\n+ *\n+ * When \\a format is empty and multiple media bus codes can produce the same\n+ * size, the media bus code with the highest bits-per-pixel is selected.\n+ *\n+ * The returned sensor output format is guaranteed to be acceptable by the\n+ * setFormat() method without any modification.\n+ *\n+ * \\return The best sensor output format matching the desired size and format\n+ * on success, or an empty format otherwise.\n+ */\n+V4L2SubdeviceFormat CamssCsiCamera::getSensorFormat(Size size, const PixelFormat &format) const\n+{\n+\tunsigned int desiredArea = size.width * size.height;\n+\tunsigned int bestArea = std::numeric_limits<unsigned int>::max();\n+\tconst Size &resolution = sensor_->resolution();\n+\tstd::vector<unsigned int> mbusCodes;\n+\tfloat desiredRatio = static_cast<float>(resolution.width) /\n+\t\t\t     resolution.height;\n+\tfloat bestRatio = std::numeric_limits<float>::max();\n+\tunsigned int desiredCode = 0;\n+\tuint32_t bestCode = 0;\n+\tuint8_t bestDepth = 0;\n+\tSize bestSize;\n+\n+\t/* If no desired size use the sensor resolution. */\n+\tif (size.isNull())\n+\t\tsize = resolution;\n+\n+\tif (format.isValid())\n+\t\tdesiredCode = PixelFormatToMbusCode(format);\n+\n+\tif (desiredCode)\n+\t\tmbusCodes.push_back(desiredCode);\n+\telse\n+\t\tmbusCodes = sensor_->mbusCodes();\n+\n+\tfor (unsigned int code : mbusCodes) {\n+\t\tPixelFormat pixelFormat = mbusCodeToPixelFormat(code);\n+\t\tBayerFormat bayerFormat = BayerFormat::fromPixelFormat(pixelFormat);\n+\n+\t\t/* Only Bayer formats are supported for now */\n+\t\tif (!bayerFormat.isValid())\n+\t\t\tcontinue;\n+\n+\t\tconst auto sizes = sensor_->sizes(code);\n+\t\tif (!sizes.size())\n+\t\t\tcontinue;\n+\n+\t\tfor (const Size &sz : sizes) {\n+\t\t\tif (sz.width < size.width || sz.height < size.height)\n+\t\t\t\tcontinue;\n+\n+\t\t\tfloat ratio = static_cast<float>(sz.width) / sz.height;\n+\t\t\t/*\n+\t\t\t * Ratios can differ by small mantissa difference which\n+\t\t\t * can affect the selection of the sensor output size\n+\t\t\t * wildly. We are interested in selection of the closest\n+\t\t\t * size with respect to the desired output size, hence\n+\t\t\t * comparing it with a single precision digit is enough.\n+\t\t\t */\n+\t\t\tratio = static_cast<unsigned int>(ratio * 10) / 10.0;\n+\t\t\tfloat ratioDiff = std::abs(ratio - desiredRatio);\n+\t\t\tunsigned int area = sz.width * sz.height;\n+\t\t\tunsigned int areaDiff = area - desiredArea;\n+\n+\t\t\tif (ratioDiff > bestRatio)\n+\t\t\t\tcontinue;\n+\n+\t\t\tif ((ratioDiff < bestRatio || areaDiff < bestArea) ||\n+\t\t\t    (ratioDiff == bestRatio && areaDiff == bestArea &&\n+\t\t\t     bayerFormat.bitDepth > bestDepth)) {\n+\t\t\t\tbestRatio = ratioDiff;\n+\t\t\t\tbestArea = areaDiff;\n+\t\t\t\tbestSize = sz;\n+\t\t\t\tbestCode = code;\n+\t\t\t\tbestDepth = bayerFormat.bitDepth;\n+\t\t\t}\n+\t\t}\n+\t}\n+\n+\tif (bestSize.isNull()) {\n+\t\tLOG(Camss, Warning) << \"No supported format or size found\";\n+\t\treturn {};\n+\t}\n+\n+\tV4L2SubdeviceFormat sensorFormat{};\n+\tsensorFormat.code = bestCode;\n+\tsensorFormat.size = bestSize;\n+\n+\treturn sensorFormat;\n+}\n+\n+StreamConfiguration CamssCsiCamera::generateConfiguration(void) const\n+{\n+\tstd::map<PixelFormat, std::vector<SizeRange>> formats;\n+\n+\tfor (unsigned int code : sensor_->mbusCodes()) {\n+\t\tPixelFormat pixelFormat = mbusCodeToPixelFormat(code);\n+\t\tif (!pixelFormat)\n+\t\t\tcontinue;\n+\n+\t\tstd::vector<SizeRange> sizes;\n+\t\tfor (const Size &sz : sensor_->sizes(code))\n+\t\t\tsizes.emplace_back(sz);\n+\n+\t\tformats[pixelFormat] = sizes;\n+\t}\n+\n+\tV4L2SubdeviceFormat sensorFormat = getSensorFormat();\n+\tStreamConfiguration cfg{ StreamFormats{ formats } };\n+\tcfg.size = sensorFormat.size;\n+\tcfg.pixelFormat = mbusCodeToPixelFormat(sensorFormat.code);\n+\tcfg.bufferCount = kBufferCount;\n+\n+\treturn cfg;\n+}\n+\n+StreamConfiguration CamssCsiCamera::validate(const StreamConfiguration &req) const\n+{\n+\tStreamConfiguration cfg;\n+\n+\t/* Query the sensor static information for closest match. */\n+\tV4L2SubdeviceFormat sensorFormat = getSensorFormat(req.size, req.pixelFormat);\n+\n+\t/* Try format to get Stride and framesize */\n+\tV4L2DeviceFormat format;\n+\tformat.fourcc = mbusCodeToV4L2PixelFormat(sensorFormat.code);\n+\tformat.size = sensorFormat.size;\n+\tformat.planesCount = 1;\n+\n+\tint ret = output_->tryFormat(&format);\n+\tif (ret < 0 || format.planesCount != 1)\n+\t\treturn {};\n+\n+\tcfg.size = format.size;\n+\tcfg.pixelFormat = format.fourcc.toPixelFormat();\n+\tcfg.stride = format.planes[0].bpl;\n+\tcfg.frameSize = format.planes[0].size;\n+\tcfg.bufferCount = std::max(kBufferCount, req.bufferCount);\n+\n+\treturn cfg;\n+}\n+\n+/**\n+ * \\brief Configure the CamssCsi unit\n+ * \\param[in] cfg Requested CamssCsi stream config from an earlier validate() call\n+ * \\param[in] transform The transformation to be applied on the image sensor\n+ * \\param[out] outputFormat The CamssCsi output V4L2DeviceFormat format\n+ * \\return 0 on success or a negative error code otherwise\n+ */\n+int CamssCsiCamera::configure(const StreamConfiguration &cfg, const Transform &transform,\n+\t\t\t      V4L2DeviceFormat *outputFormat)\n+{\n+\tV4L2SubdeviceFormat sensorFormat;\n+\tint ret;\n+\n+\tsensorFormat = getSensorFormat(cfg.size, cfg.pixelFormat);\n+\t/* This updates sensorFormat with the actual established format */\n+\tret = sensor_->setFormat(&sensorFormat, transform);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tfor (auto &link : links_) {\n+\t\tif (!(link.link->flags() & MEDIA_LNK_FL_ENABLED)) {\n+\t\t\tret = link.link->setEnabled(true);\n+\t\t\tif (ret)\n+\t\t\t\treturn ret;\n+\t\t}\n+\n+\t\tMediaPad *sink = link.link->sink();\n+\t\tret = link.sinkSubdev->setFormat(sink->index(), &sensorFormat,\n+\t\t\t\t\t\t V4L2Subdevice::ActiveFormat);\n+\t\tif (ret)\n+\t\t\treturn ret;\n+\t}\n+\n+\toutputFormat->fourcc = mbusCodeToV4L2PixelFormat(sensorFormat.code);\n+\toutputFormat->size = sensorFormat.size;\n+\toutputFormat->planesCount = 1;\n+\n+\t/* This updates outputFormat with the actual established format */\n+\tret = output_->setFormat(outputFormat);\n+\tif (ret)\n+\t\treturn ret;\n+\n+\tbufferCount_ = cfg.bufferCount;\n+\n+\treturn 0;\n+}\n+\n+int CamssCsiCamera::exportBuffers(unsigned int count,\n+\t\t\t\t  std::vector<std::unique_ptr<FrameBuffer>> *buffers)\n+{\n+\treturn output_->exportBuffers(count, buffers);\n+}\n+\n+int CamssCsiCamera::start()\n+{\n+\tint ret = output_->exportBuffers(bufferCount_, &buffers_);\n+\tif (ret < 0)\n+\t\treturn ret;\n+\n+\tret = output_->importBuffers(bufferCount_);\n+\tif (ret)\n+\t\tLOG(Camss, Error) << \"Failed to import CamssCsi buffers\";\n+\n+\tfor (std::unique_ptr<FrameBuffer> &buffer : buffers_)\n+\t\tavailableBuffers_.push(buffer.get());\n+\n+\tret = output_->streamOn();\n+\tif (ret) {\n+\t\tfreeBuffers();\n+\t\treturn ret;\n+\t}\n+\n+\treturn 0;\n+}\n+\n+void CamssCsiCamera::stop()\n+{\n+\tif (output_->streamOff())\n+\t\tLOG(Camss, Error) << \"CamssCsi stream off failed\";\n+\n+\tfreeBuffers();\n+}\n+\n+FrameBuffer *CamssCsiCamera::queueBuffer(Request *request, FrameBuffer *rawBuffer)\n+{\n+\tFrameBuffer *buffer = rawBuffer;\n+\n+\t/* If no buffer is provided in the request, use an internal one. */\n+\tif (!buffer) {\n+\t\tif (availableBuffers_.empty()) {\n+\t\t\tLOG(Camss, Debug) << \"CamssCsi buffer underrun\";\n+\t\t\treturn nullptr;\n+\t\t}\n+\n+\t\tbuffer = availableBuffers_.front();\n+\t\tavailableBuffers_.pop();\n+\t\tbuffer->_d()->setRequest(request);\n+\t}\n+\n+\tint ret = output_->queueBuffer(buffer);\n+\tif (ret)\n+\t\treturn nullptr;\n+\n+\treturn buffer;\n+}\n+\n+void CamssCsiCamera::tryReturnBuffer(FrameBuffer *buffer)\n+{\n+\t/*\n+\t * \\todo Once more pipelines deal with buffers that may be allocated\n+\t * internally or externally this pattern might become a common need. At\n+\t * that point this check should be moved to something clever in\n+\t * FrameBuffer.\n+\t */\n+\tfor (const std::unique_ptr<FrameBuffer> &buf : buffers_) {\n+\t\tif (buf.get() == buffer) {\n+\t\t\tavailableBuffers_.push(buffer);\n+\t\t\tbreak;\n+\t\t}\n+\t}\n+\n+\tbufferAvailable.emit();\n+}\n+\n+void CamssCsiCamera::freeBuffers()\n+{\n+\tavailableBuffers_ = {};\n+\tbuffers_.clear();\n+\n+\tif (output_->releaseBuffers())\n+\t\tLOG(Camss, Error) << \"Failed to release CamssCsi buffers\";\n+}\n+\n+CamssCsi::CamssCsi()\n+{\n+}\n+\n+void CamssCsi::getEntities(std::vector<MediaEntity *> &ents, const char *fmt, unsigned int max)\n+{\n+\tfor (unsigned int i = 0; i < max; i++) {\n+\t\tchar name[16];\n+\t\tsnprintf(name, sizeof(name), fmt, i);\n+\t\tMediaEntity *ent = camssMediaDev_->getEntityByName(name);\n+\t\tif (ent)\n+\t\t\tents.push_back(ent);\n+\t}\n+}\n+\n+CamssCsi::Cameras CamssCsi::match(PipelineHandler *pipe, DeviceEnumerator *enumerator)\n+{\n+\tDeviceMatch camssDm(\"qcom-camss\");\n+\tCameras cameras;\n+\n+\t/*\n+\t * On SoCs where the CSI-phy is a separate dt-node (e.g. x1e), only\n+\t * actually used phys are there. So no match on \"msm_csiphy%d\".\n+\t */\n+\n+\tfor (unsigned int i = 0; i < kMinCsiDecoders; i++)\n+\t\tcamssDm.add(\"msm_csid\" + std::to_string(i));\n+\n+\tfor (unsigned int i = 0; i < kMinVfes; i++) {\n+\t\tcamssDm.add(\"msm_vfe\" + std::to_string(i) + \"_rdi0\");\n+\t\tcamssDm.add(\"msm_vfe\" + std::to_string(i) + \"_rdi1\");\n+\t\tcamssDm.add(\"msm_vfe\" + std::to_string(i) + \"_rdi2\");\n+\t\tcamssDm.add(\"msm_vfe\" + std::to_string(i) + \"_pix\");\n+\t}\n+\n+\tcamssMediaDev_ = pipe->acquireMediaDevice(enumerator, camssDm);\n+\tif (!camssMediaDev_)\n+\t\treturn {};\n+\n+\t/*\n+\t * Disable all links that are enabled to start with a clean state,\n+\t * CamssCsiCamera::configure() enables links as necessary.\n+\t * \\todo instead only disable links on used entities, to allow\n+\t * 2 separate libcamera instances to drive 2 different sensors.\n+\t * This will also require changes to PipelineHandler::acquire() to\n+\t * allow a more fine grained version of that locking a list of\n+\t * subdevs associated with a Camera instead of the mediactl node.\n+\t */\n+\tif (camssMediaDev_->disableLinks())\n+\t\treturn {};\n+\n+\tgetEntities(phys_, \"msm_csiphy%d\", kMaxCsiPhys);\n+\tgetEntities(csids_, \"msm_csid%d\", kMaxCsiDecoders);\n+\t/* Only RDI0 is used for now */\n+\tgetEntities(vfes_, \"msm_vfe%d_rdi0\", kMaxVfes);\n+\n+\tLOG(Camss, Info) << \"Found \"\n+\t\t\t << phys_.size() << \" CSI phy(s) \"\n+\t\t\t << csids_.size() << \" CSI decoders \"\n+\t\t\t << vfes_.size() << \" VFEs\";\n+\n+\tfor (auto &phy : phys_) {\n+\t\tstd::unique_ptr<CamssCsiCamera> camera = enumCamera(phy);\n+\t\tif (camera)\n+\t\t\tcameras.push_back(std::move(camera));\n+\t}\n+\n+\treturn cameras;\n+}\n+\n+std::unique_ptr<CamssCsiCamera> CamssCsi::enumCamera(MediaEntity *phy)\n+{\n+\tstd::unique_ptr<CamssCsiCamera> cam = std::make_unique<CamssCsiCamera>();\n+\tint ret;\n+\n+\t/* CSI phy has a sink pad for the sensor at index 0. */\n+\tif (phy->pads().empty() || phy->pads()[0]->links().empty())\n+\t\treturn nullptr;\n+\n+\tMediaEntity *sensor =\n+\t\tphy->pads()[0]->links()[0]->source()->entity();\n+\tcam->sensor_ = CameraSensorFactoryBase::create(sensor);\n+\tif (!cam->sensor_)\n+\t\treturn nullptr;\n+\n+\tif (csids_.empty()) {\n+\t\tLOG(Camss, Warning)\n+\t\t\t<< \"Not enough CSI decoders to enumerate all cameras\\n\";\n+\t\treturn nullptr;\n+\t}\n+\n+\tif (vfes_.empty()) {\n+\t\tLOG(Camss, Warning)\n+\t\t\t<< \"Not enough VFEs to enumerate all cameras\\n\";\n+\t\treturn nullptr;\n+\t}\n+\n+\tMediaEntity *csid = csids_.front();\n+\tMediaEntity *vfe = vfes_.front();\n+\n+\tfor (unsigned int i = 0; i < CamssCsiCamera::LinkCount; i++) {\n+\t\tauto &link = cam->links_[i];\n+\n+\t\tswitch (i) {\n+\t\tcase CamssCsiCamera::SensorPhyLink:\n+\t\t\tlink.link = camssMediaDev_->link(sensor, 0, phy, 0);\n+\t\t\tlink.sinkSubdev = std::make_unique<V4L2Subdevice>(phy);\n+\t\t\tbreak;\n+\t\tcase CamssCsiCamera::PhyCsidLink:\n+\t\t\tlink.link = camssMediaDev_->link(phy, 1, csid, 0);\n+\t\t\tlink.sinkSubdev = std::make_unique<V4L2Subdevice>(csid);\n+\t\t\tbreak;\n+\t\tcase CamssCsiCamera::CsidVfeLink:\n+\t\t\tlink.link = camssMediaDev_->link(csid, 1, vfe, 0);\n+\t\t\tlink.sinkSubdev = std::make_unique<V4L2Subdevice>(vfe);\n+\t\t\tbreak;\n+\t\t}\n+\n+\t\tif (!link.link) {\n+\t\t\tLOG(Camss, Error) << \"Error enumerating links\";\n+\t\t\treturn nullptr;\n+\t\t}\n+\n+\t\tret = link.sinkSubdev->open();\n+\t\tif (ret)\n+\t\t\treturn nullptr;\n+\t}\n+\n+\t/* VFE has a source pad to its /dev/video# node at index 1. */\n+\tif (vfe->pads().size() < 2 || vfe->pads()[1]->links().empty())\n+\t\treturn nullptr;\n+\n+\tMediaEntity *output =\n+\t\tvfe->pads()[1]->links()[0]->sink()->entity();\n+\tcam->output_ = std::make_unique<V4L2VideoDevice>(output);\n+\tret = cam->output_->open();\n+\tif (ret)\n+\t\treturn nullptr;\n+\n+\tLOG(Camss, Info)\n+\t\t<< \"Sensor \" << cam->sensor_->entity()->name()\n+\t\t<< \" phy \" << phy->name()\n+\t\t<< \" decoder \" << csid->name()\n+\t\t<< \" VFE \" << vfe->name();\n+\n+\tcsids_.erase(csids_.begin());\n+\tvfes_.erase(vfes_.begin());\n+\treturn cam;\n+}\n+\n+} /* namespace libcamera */\ndiff --git a/src/libcamera/pipeline/camss/camss_csi.h b/src/libcamera/pipeline/camss/camss_csi.h\nnew file mode 100644\nindex 000000000..4b1b0f83b\n--- /dev/null\n+++ b/src/libcamera/pipeline/camss/camss_csi.h\n@@ -0,0 +1,125 @@\n+/* SPDX-License-Identifier: LGPL-2.1-or-later */\n+/*\n+ * Qualcomm CAMSS CSI phy/decoder and VFE handling\n+ *\n+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.\n+ *\n+ * Partially based on other pipeline-handlers which are:\n+ * Copyright (C) 2020, Laurent Pinchart\n+ * Copyright (C) 2019, Martijn Braam\n+ * Copyright (C) 2019, Google Inc.\n+ */\n+\n+#pragma once\n+\n+#include <memory>\n+#include <queue>\n+#include <vector>\n+\n+#include <libcamera/base/signal.h>\n+\n+#include \"libcamera/internal/device_enumerator.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+class CameraSensor;\n+class FrameBuffer;\n+class MediaDevice;\n+class PixelFormat;\n+class Request;\n+class Size;\n+class SizeRange;\n+struct StreamConfiguration;\n+enum class Transform;\n+\n+class CamssCsiCamera\n+{\n+public:\n+\tCamssCsiCamera();\n+\n+\tStreamConfiguration generateConfiguration(void) const;\n+\tStreamConfiguration validate(const StreamConfiguration &req) const;\n+\tint configure(const StreamConfiguration &cfg, const Transform &transform,\n+\t\t      V4L2DeviceFormat *outputFormat);\n+\tint exportBuffers(unsigned int count,\n+\t\t\t  std::vector<std::unique_ptr<FrameBuffer>> *buffers);\n+\n+\tPixelFormat mbusCodeToPixelFormat(unsigned int code) const;\n+\tunsigned int PixelFormatToMbusCode(const PixelFormat &format) const;\n+\n+\tint start();\n+\tvoid stop();\n+\n+\tCameraSensor *sensor() { return sensor_.get(); }\n+\tconst CameraSensor *sensor() const { return sensor_.get(); }\n+\n+\tFrameBuffer *queueBuffer(Request *request, FrameBuffer *rawBuffer);\n+\tvoid tryReturnBuffer(FrameBuffer *buffer);\n+\tSignal<FrameBuffer *> &bufferReady() { return output_->bufferReady; }\n+\t/*\n+\t * \\todo camss kernel driver does not support this atm. Once supported\n+\t * this needs to take frameStart signal from the csi-decoder.\n+\t */\n+\tSignal<uint32_t> &frameStart() { return links_[0].sinkSubdev->frameStart; }\n+\tbool supportsFrameStart() { return false; }\n+\n+\tSignal<> bufferAvailable;\n+\tStream rawStream_;\n+\n+private:\n+\tfriend class CamssCsi;\n+\n+\tstatic constexpr unsigned int kBufferCount = 4;\n+\n+\t/* 3 links: sensor -> phy, phy -> csid, csid->vfe */\n+\tenum LinkIndex {\n+\t\tSensorPhyLink,\n+\t\tPhyCsidLink,\n+\t\tCsidVfeLink,\n+\t\tLinkCount\n+\t};\n+\n+\tstruct linkInfo {\n+\t\tMediaLink *link;\n+\t\tstd::unique_ptr<V4L2Subdevice> sinkSubdev;\n+\t};\n+\n+\tV4L2PixelFormat mbusCodeToV4L2PixelFormat(unsigned int code) const;\n+\tV4L2SubdeviceFormat getSensorFormat(Size size = {}, const PixelFormat &format = {}) const;\n+\tvoid freeBuffers();\n+\n+\tstd::unique_ptr<CameraSensor> sensor_;\n+\tstd::unique_ptr<V4L2VideoDevice> output_;\n+\tstd::array<linkInfo, LinkCount> links_;\n+\n+\tstd::vector<std::unique_ptr<FrameBuffer>> buffers_;\n+\tstd::queue<FrameBuffer *> availableBuffers_;\n+\tunsigned int bufferCount_;\n+};\n+\n+class CamssCsi\n+{\n+public:\n+\tusing Cameras = std::vector<std::unique_ptr<CamssCsiCamera>>;\n+\n+\tCamssCsi();\n+\tCameras match(PipelineHandler *pipe, DeviceEnumerator *enumerator);\n+private:\n+\tvoid getEntities(std::vector<MediaEntity *> &ents, const char *fmt, unsigned int max);\n+\tstd::unique_ptr<CamssCsiCamera> enumCamera(MediaEntity *phy);\n+\n+\tstatic constexpr unsigned int kMaxCsiPhys = 5;\n+\tstatic constexpr unsigned int kMinCsiDecoders = 2;\n+\tstatic constexpr unsigned int kMaxCsiDecoders = 5;\n+\tstatic constexpr unsigned int kMinVfes = 2;\n+\tstatic constexpr unsigned int kMaxVfes = 4;\n+\tstd::shared_ptr<MediaDevice> camssMediaDev_;\n+\tstd::vector<MediaEntity *> phys_;\n+\tstd::vector<MediaEntity *> csids_;\n+\tstd::vector<MediaEntity *> vfes_;\n+};\n+\n+} /* namespace libcamera */\ndiff --git a/src/libcamera/pipeline/camss/camss_frames.cpp b/src/libcamera/pipeline/camss/camss_frames.cpp\nnew file mode 100644\nindex 000000000..09630b053\n--- /dev/null\n+++ b/src/libcamera/pipeline/camss/camss_frames.cpp\n@@ -0,0 +1,106 @@\n+/* SPDX-License-Identifier: LGPL-2.1-or-later */\n+/*\n+ * Camss Frames helper\n+ *\n+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.\n+ *\n+ * Based on the IPU3 pipeline-handler which is:\n+ * Copyright (C) 2020, Google Inc.\n+ */\n+\n+#include \"camss_frames.h\"\n+\n+#include <libcamera/base/log.h>\n+\n+#include <libcamera/framebuffer.h>\n+#include <libcamera/request.h>\n+\n+#include \"libcamera/internal/framebuffer.h\"\n+#include \"libcamera/internal/pipeline_handler.h\"\n+\n+namespace libcamera {\n+\n+LOG_DECLARE_CATEGORY(Camss)\n+\n+CamssFrames::CamssFrames()\n+{\n+}\n+\n+void CamssFrames::init()\n+{\n+\tframeInfo_.clear();\n+}\n+\n+void CamssFrames::clear()\n+{\n+}\n+\n+CamssFrames::Info *CamssFrames::create(Request *request)\n+{\n+\tunsigned int id = request->sequence();\n+\n+\tauto [it, inserted] = frameInfo_.try_emplace(id);\n+\tASSERT(inserted);\n+\n+\tauto &info = it->second;\n+\n+\tinfo.id = id;\n+\tinfo.request = request;\n+\tinfo.rawBuffer = nullptr;\n+\tinfo.metadataProcessed = false;\n+\n+\treturn &info;\n+}\n+\n+void CamssFrames::remove(CamssFrames::Info *info)\n+{\n+\t/* Delete the extended frame information. */\n+\tframeInfo_.erase(info->id);\n+}\n+\n+bool CamssFrames::tryComplete(CamssFrames::Info *info)\n+{\n+\tRequest *request = info->request;\n+\n+\tif (request->hasPendingBuffers())\n+\t\treturn false;\n+\n+\tif (!info->metadataProcessed)\n+\t\treturn false;\n+\n+\tremove(info);\n+\n+\tbufferAvailable.emit();\n+\n+\treturn true;\n+}\n+\n+CamssFrames::Info *CamssFrames::find(unsigned int id)\n+{\n+\tconst auto &itInfo = frameInfo_.find(id);\n+\n+\tif (itInfo != frameInfo_.end())\n+\t\treturn &itInfo->second;\n+\n+\tLOG(Camss, Fatal) << \"Can't find tracking information for frame \" << id;\n+\n+\treturn nullptr;\n+}\n+\n+CamssFrames::Info *CamssFrames::find(FrameBuffer *buffer)\n+{\n+\tfor (auto &[id, info] : frameInfo_) {\n+\t\tfor (const auto &[stream, buf] : info.request->buffers())\n+\t\t\tif (buf == buffer)\n+\t\t\t\treturn &info;\n+\n+\t\tif (info.rawBuffer == buffer)\n+\t\t\treturn &info;\n+\t}\n+\n+\tLOG(Camss, Fatal) << \"Can't find tracking information from buffer\";\n+\n+\treturn nullptr;\n+}\n+\n+} /* namespace libcamera */\ndiff --git a/src/libcamera/pipeline/camss/camss_frames.h b/src/libcamera/pipeline/camss/camss_frames.h\nnew file mode 100644\nindex 000000000..eb630fbcd\n--- /dev/null\n+++ b/src/libcamera/pipeline/camss/camss_frames.h\n@@ -0,0 +1,59 @@\n+/* SPDX-License-Identifier: LGPL-2.1-or-later */\n+/*\n+ * Camss Frames helper\n+ *\n+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.\n+ *\n+ * Based on the IPU3 pipeline-handler which is:\n+ * Copyright (C) 2020, Google Inc.\n+ */\n+\n+#pragma once\n+\n+#include <map>\n+#include <memory>\n+#include <queue>\n+#include <vector>\n+\n+#include <libcamera/base/signal.h>\n+\n+#include <libcamera/controls.h>\n+\n+namespace libcamera {\n+\n+class FrameBuffer;\n+class Request;\n+\n+class CamssFrames\n+{\n+public:\n+\tstruct Info {\n+\t\tunsigned int id;\n+\t\tRequest *request;\n+\n+\t\tFrameBuffer *rawBuffer;\n+\n+\t\tControlList effectiveSensorControls;\n+\n+\t\tbool metadataProcessed;\n+\t};\n+\n+\tCamssFrames();\n+\n+\tvoid init();\n+\tvoid clear();\n+\n+\tInfo *create(Request *request);\n+\tvoid remove(Info *info);\n+\tbool tryComplete(Info *info);\n+\n+\tInfo *find(unsigned int id);\n+\tInfo *find(FrameBuffer *buffer);\n+\n+\tSignal<> bufferAvailable;\n+\n+private:\n+\tstd::map<unsigned int, Info> frameInfo_;\n+};\n+\n+} /* namespace libcamera */\ndiff --git a/src/libcamera/pipeline/camss/camss_isp.cpp b/src/libcamera/pipeline/camss/camss_isp.cpp\nnew file mode 100644\nindex 000000000..c452568d6\n--- /dev/null\n+++ b/src/libcamera/pipeline/camss/camss_isp.cpp\n@@ -0,0 +1,26 @@\n+/* SPDX-License-Identifier: LGPL-2.1-or-later */\n+/*\n+ * Qualcomm CAMSS ISP virtual base class\n+ *\n+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.\n+ */\n+\n+#include \"camss_isp.h\"\n+\n+namespace libcamera {\n+\n+/**\n+ * \\var CamssIsp::inputBufferReady\n+ * \\brief A signal emitted when the input frame buffer completes\n+ */\n+\n+/**\n+ * \\var CamssIsp::outputBufferReady\n+ * \\brief A signal emitted when the output frame buffer completes\n+ */\n+\n+CamssIsp::~CamssIsp()\n+{\n+}\n+\n+} /* namespace libcamera */\ndiff --git a/src/libcamera/pipeline/camss/camss_isp.h b/src/libcamera/pipeline/camss/camss_isp.h\nnew file mode 100644\nindex 000000000..7c856eeea\n--- /dev/null\n+++ b/src/libcamera/pipeline/camss/camss_isp.h\n@@ -0,0 +1,59 @@\n+/* SPDX-License-Identifier: LGPL-2.1-or-later */\n+/*\n+ * Qualcomm CAMSS ISP virtual base class\n+ *\n+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.\n+ */\n+\n+#pragma once\n+\n+#include <stdint.h>\n+#include <vector>\n+\n+#include <libcamera/base/signal.h>\n+\n+#include <libcamera/stream.h>\n+\n+namespace libcamera {\n+\n+class ControlList;\n+class FrameBuffer;\n+struct StreamConfiguration;\n+struct V4L2DeviceFormat;\n+\n+class CamssIsp\n+{\n+public:\n+\tvirtual ~CamssIsp() = 0;\n+\n+\tvirtual bool isValid() = 0;\n+\n+\tvirtual StreamConfiguration generateConfiguration(const StreamConfiguration &raw) const = 0;\n+\tvirtual StreamConfiguration validate(const StreamConfiguration &raw, const StreamConfiguration &req) const = 0;\n+\tvirtual int configure(const StreamConfiguration &inputCfg,\n+\t\t\t      const StreamConfiguration &outputCfg) = 0;\n+\n+\tvirtual int allocateBuffers([[maybe_unused]] unsigned int bufferCount) { return 0; }\n+\tvirtual void freeBuffers() {}\n+\tvirtual int exportOutputBuffers([[maybe_unused]] const Stream *stream,\n+\t\t\t\t\t[[maybe_unused]] unsigned int count,\n+\t\t\t\t\t[[maybe_unused]] std::vector<std::unique_ptr<FrameBuffer>> *buffers) { return 0; }\n+\tvirtual void queueBuffers(Request *request, FrameBuffer *input) = 0;\n+\n+\tvirtual void processStats(const uint32_t frame, const uint32_t bufferId,\n+\t\t\t\t  const ControlList &sensorControls) = 0;\n+\n+\tvirtual int start() = 0;\n+\tvirtual void stop() = 0;\n+\n+\tstatic constexpr unsigned int kBufferCount = 4;\n+\n+\tSignal<FrameBuffer *> inputBufferReady;\n+\tSignal<FrameBuffer *> outputBufferReady;\n+\tSignal<uint32_t, uint32_t> statsReady;\n+\tSignal<uint32_t, const ControlList &> metadataReady;\n+\tSignal<const ControlList &> setSensorControls;\n+\tStream outStream_;\n+};\n+\n+} /* namespace libcamera */\ndiff --git a/src/libcamera/pipeline/camss/camss_isp_soft.cpp b/src/libcamera/pipeline/camss/camss_isp_soft.cpp\nnew file mode 100644\nindex 000000000..363cb29b9\n--- /dev/null\n+++ b/src/libcamera/pipeline/camss/camss_isp_soft.cpp\n@@ -0,0 +1,203 @@\n+/* SPDX-License-Identifier: LGPL-2.1-or-later */\n+/*\n+ * Qualcomm CAMSS softISP class\n+ *\n+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.\n+ */\n+\n+#include \"camss_isp_soft.h\"\n+\n+#include <libcamera/base/log.h>\n+\n+#include <libcamera/controls.h>\n+#include <libcamera/geometry.h>\n+#include <libcamera/request.h>\n+#include <libcamera/stream.h>\n+\n+#include \"libcamera/internal/camera_sensor.h\"\n+#include \"libcamera/internal/pipeline_handler.h\"\n+#include \"libcamera/internal/software_isp/software_isp.h\"\n+#include \"libcamera/internal/v4l2_videodevice.h\"\n+\n+namespace libcamera {\n+\n+LOG_DECLARE_CATEGORY(Camss)\n+\n+/**\n+ * \\class CamssIspSoft\n+ * \\brief CAMSS ISP class using the Software ISP\n+ */\n+\n+/**\n+ * \\brief Constructs CamssIspSoft object\n+ * \\param[in] pipe The pipeline handler in use\n+ * \\param[in] sensor Pointer to the CameraSensor instance owned by the pipeline\n+ * \\param[out] ControlInfoMap to which to add ISP provided controls\n+ */\n+CamssIspSoft::CamssIspSoft(PipelineHandler *pipe, const CameraSensor *sensor, ControlInfoMap *ispControls)\n+\t: sensor_(sensor)\n+{\n+\tswIsp_ = std::make_unique<SoftwareIsp>(pipe, sensor, ispControls);\n+\n+\tswIsp_->inputBufferReady.connect(this,\n+\t\t\t\t\t [&](FrameBuffer *f) { inputBufferReady.emit(f); });\n+\tswIsp_->outputBufferReady.connect(this,\n+\t\t\t\t\t  [&](FrameBuffer *f) { outputBufferReady.emit(f); });\n+\tswIsp_->ispStatsReady.connect(this,\n+\t\t\t\t      [&](uint32_t frame, uint32_t bufferId) {\n+\t\t\t\t\t      statsReady.emit(frame, bufferId);\n+\t\t\t\t      });\n+\tswIsp_->metadataReady.connect(this,\n+\t\t\t\t      [&](uint32_t frame, const ControlList &metadata) {\n+\t\t\t\t\t      metadataReady.emit(frame, metadata);\n+\t\t\t\t      });\n+\tswIsp_->setSensorControls.connect(this,\n+\t\t\t\t\t  [&](const ControlList &sensorControls) {\n+\t\t\t\t\t\t  setSensorControls.emit(sensorControls);\n+\t\t\t\t\t  });\n+}\n+\n+CamssIspSoft::~CamssIspSoft() = default;\n+\n+bool CamssIspSoft::isValid()\n+{\n+\treturn swIsp_->isValid();\n+}\n+\n+StreamConfiguration CamssIspSoft::generateConfiguration(const StreamConfiguration &raw) const\n+{\n+\t/*\n+\t * The raw stream config may contain multiple format <-> sizes tupples.\n+\t * Since the softIsp can always crop / downscale and since it supports\n+\t * the same set of output pixel-formats for all supported input pixel-\n+\t * formats, simply use the best size, which should be preset in raw.size\n+\t * and also use the input pixel-format which matches that.\n+\t */\n+\tSizeRange sizes = swIsp_->sizes(raw.pixelFormat, raw.size);\n+\tstd::vector<PixelFormat> pixelFormats = swIsp_->formats(raw.pixelFormat);\n+\n+\tif (sizes.max.isNull() || pixelFormats.empty())\n+\t\treturn {};\n+\n+\tstd::vector<SizeRange> sizesVector = { sizes };\n+\tstd::map<PixelFormat, std::vector<SizeRange>> formats;\n+\n+\tfor (unsigned int i = 0; i < pixelFormats.size(); i++)\n+\t\tformats[pixelFormats[i]] = sizesVector;\n+\n+\tStreamConfiguration cfg{ StreamFormats{ formats } };\n+\tcfg.size = sizes.max;\n+\tcfg.pixelFormat = pixelFormats[0];\n+\tcfg.bufferCount = kBufferCount;\n+\n+\treturn cfg;\n+}\n+\n+namespace {\n+\n+/*\n+ * \\todo copy-pasted from src/libcamera/pipeline/simple/simple.cpp turn this\n+ * into a member of SizeRange ?\n+ * \\todo also see V4L2M2MConverter::adjustSizes() which is also similar.\n+ */\n+static Size adjustSize(const Size &requestedSize, const SizeRange &supportedSizes)\n+{\n+\tASSERT(supportedSizes.min <= supportedSizes.max);\n+\n+\tif (supportedSizes.min == supportedSizes.max)\n+\t\treturn supportedSizes.max;\n+\n+\tunsigned int hStep = supportedSizes.hStep;\n+\tunsigned int vStep = supportedSizes.vStep;\n+\n+\tif (hStep == 0)\n+\t\thStep = supportedSizes.max.width - supportedSizes.min.width;\n+\tif (vStep == 0)\n+\t\tvStep = supportedSizes.max.height - supportedSizes.min.height;\n+\n+\tSize adjusted = requestedSize.boundedTo(supportedSizes.max)\n+\t\t\t\t.expandedTo(supportedSizes.min);\n+\n+\treturn adjusted.shrunkBy(supportedSizes.min)\n+\t\t.alignedDownTo(hStep, vStep)\n+\t\t.grownBy(supportedSizes.min);\n+}\n+\n+} /* namespace */\n+\n+StreamConfiguration CamssIspSoft::validate(const StreamConfiguration &raw, const StreamConfiguration &req) const\n+{\n+\tStreamConfiguration cfg;\n+\n+\tSizeRange sizes = swIsp_->sizes(raw.pixelFormat, raw.size);\n+\tstd::vector<PixelFormat> formats = swIsp_->formats(raw.pixelFormat);\n+\n+\tcfg.size = adjustSize(req.size, sizes);\n+\n+\tif (cfg.size.isNull() || formats.empty())\n+\t\treturn {};\n+\n+\tfor (unsigned int i = 0; i < formats.size(); i++) {\n+\t\tif (formats[i] == req.pixelFormat)\n+\t\t\tcfg.pixelFormat = req.pixelFormat;\n+\t}\n+\n+\tif (!cfg.pixelFormat.isValid())\n+\t\tcfg.pixelFormat = formats[0];\n+\n+\tstd::tie(cfg.stride, cfg.frameSize) =\n+\t\tswIsp_->strideAndFrameSize(cfg.pixelFormat, cfg.size);\n+\n+\tcfg.bufferCount = std::max(kBufferCount, req.bufferCount);\n+\n+\treturn cfg;\n+}\n+\n+int CamssIspSoft::configure(const StreamConfiguration &inputCfg,\n+\t\t\t    const StreamConfiguration &outputCfg)\n+{\n+\tstd::vector<std::reference_wrapper<const StreamConfiguration>> outputCfgs;\n+\toutputCfgs.push_back(outputCfg);\n+\n+\t/* \\todo refactor SoftwareIsp to remove the need to pass this */\n+\tipa::soft::IPAConfigInfo configInfo;\n+\tconfigInfo.sensorControls = sensor_->controls();\n+\n+\treturn swIsp_->configure(inputCfg, outputCfgs, configInfo);\n+}\n+\n+int CamssIspSoft::exportOutputBuffers(const Stream *stream, unsigned int count,\n+\t\t\t\t      std::vector<std::unique_ptr<FrameBuffer>> *buffers)\n+{\n+\treturn swIsp_->exportBuffers(stream, count, buffers);\n+}\n+\n+void CamssIspSoft::queueBuffers(Request *request, FrameBuffer *input)\n+{\n+\tstd::map<const Stream *, FrameBuffer *> outputs;\n+\tfor (const auto &[stream, outbuffer] : request->buffers()) {\n+\t\tif (stream == &outStream_)\n+\t\t\toutputs[stream] = outbuffer;\n+\t}\n+\n+\tswIsp_->queueRequest(request->sequence(), request->controls());\n+\tswIsp_->queueBuffers(request->sequence(), input, outputs);\n+}\n+\n+void CamssIspSoft::processStats(const uint32_t frame, const uint32_t bufferId,\n+\t\t\t\tconst ControlList &sensorControls)\n+{\n+\tswIsp_->processStats(frame, bufferId, sensorControls);\n+}\n+\n+int CamssIspSoft::start()\n+{\n+\treturn swIsp_->start();\n+}\n+\n+void CamssIspSoft::stop()\n+{\n+\tswIsp_->stop();\n+}\n+\n+} /* namespace libcamera */\ndiff --git a/src/libcamera/pipeline/camss/camss_isp_soft.h b/src/libcamera/pipeline/camss/camss_isp_soft.h\nnew file mode 100644\nindex 000000000..2ba38fa1a\n--- /dev/null\n+++ b/src/libcamera/pipeline/camss/camss_isp_soft.h\n@@ -0,0 +1,50 @@\n+/* SPDX-License-Identifier: LGPL-2.1-or-later */\n+/*\n+ * Qualcomm CAMSS softISP class\n+ *\n+ * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.\n+ */\n+\n+#pragma once\n+\n+#include <memory>\n+#include <vector>\n+\n+#include \"camss_isp.h\"\n+\n+namespace libcamera {\n+\n+class CameraSensor;\n+class ControlInfoMap;\n+class PipelineHandler;\n+class SoftwareIsp;\n+\n+class CamssIspSoft : public CamssIsp\n+{\n+public:\n+\tCamssIspSoft(PipelineHandler *pipe, const CameraSensor *sensor, ControlInfoMap *ispControls);\n+\t~CamssIspSoft() override;\n+\n+\tbool isValid() override;\n+\n+\tStreamConfiguration generateConfiguration(const StreamConfiguration &raw) const override;\n+\tStreamConfiguration validate(const StreamConfiguration &raw, const StreamConfiguration &req) const override;\n+\tint configure(const StreamConfiguration &inputCfg,\n+\t\t      const StreamConfiguration &outputCfg) override;\n+\n+\tint exportOutputBuffers(const Stream *stream, unsigned int count,\n+\t\t\t\tstd::vector<std::unique_ptr<FrameBuffer>> *buffers) override;\n+\tvoid queueBuffers(Request *request, FrameBuffer *input) override;\n+\n+\tvoid processStats(const uint32_t frame, const uint32_t bufferId,\n+\t\t\t  const ControlList &sensorControls) override;\n+\n+\tint start() override;\n+\tvoid stop() override;\n+\n+private:\n+\tstd::unique_ptr<SoftwareIsp> swIsp_;\n+\tconst CameraSensor *sensor_;\n+};\n+\n+} /* namespace libcamera */\ndiff --git a/src/libcamera/pipeline/camss/meson.build b/src/libcamera/pipeline/camss/meson.build\nnew file mode 100644\nindex 000000000..047559789\n--- /dev/null\n+++ b/src/libcamera/pipeline/camss/meson.build\n@@ -0,0 +1,9 @@\n+# SPDX-License-Identifier: CC0-1.0\n+\n+libcamera_internal_sources += files([\n+    'camss.cpp',\n+    'camss_csi.cpp',\n+    'camss_frames.cpp',\n+    'camss_isp.cpp',\n+    'camss_isp_soft.cpp',\n+])\ndiff --git a/src/libcamera/pipeline/simple/simple.cpp b/src/libcamera/pipeline/simple/simple.cpp\nindex 812ff7969..4dbb39c5a 100644\n--- a/src/libcamera/pipeline/simple/simple.cpp\n+++ b/src/libcamera/pipeline/simple/simple.cpp\n@@ -263,7 +263,7 @@ static const SimplePipelineInfo supportedDevices[] = {\n \t{ \"j721e-csi2rx\", {}, true },\n \t{ \"mtk-seninf\", { { \"mtk-mdp\", 3 } }, false },\n \t{ \"mxc-isi\", {}, false },\n-\t{ \"qcom-camss\", {}, true },\n+\t//\t{ \"qcom-camss\", {}, true },\n \t{ \"sun6i-csi\", {}, false },\n };\n \n","prefixes":["RFC","1/3"]}