Show a patch.

GET /api/patches/17793/?format=api
HTTP 200 OK
Allow: GET, PUT, PATCH, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept

{
    "id": 17793,
    "url": "https://patchwork.libcamera.org/api/patches/17793/?format=api",
    "web_url": "https://patchwork.libcamera.org/patch/17793/",
    "project": {
        "id": 1,
        "url": "https://patchwork.libcamera.org/api/projects/1/?format=api",
        "name": "libcamera",
        "link_name": "libcamera",
        "list_id": "libcamera_core",
        "list_email": "libcamera-devel@lists.libcamera.org",
        "web_url": "",
        "scm_url": "",
        "webscm_url": ""
    },
    "msgid": "<20221115090755.2921-2-naush@raspberrypi.com>",
    "date": "2022-11-15T09:07:48",
    "name": "[libcamera-devel,v6,1/8] pipeline: raspberrypi: Fork DelayedControls",
    "commit_ref": null,
    "pull_url": null,
    "state": "accepted",
    "archived": false,
    "hash": "8d213da74bece3b49d11cc2b0130ecd5d3a3c986",
    "submitter": {
        "id": 34,
        "url": "https://patchwork.libcamera.org/api/people/34/?format=api",
        "name": "Naushir Patuck",
        "email": "naush@raspberrypi.com"
    },
    "delegate": null,
    "mbox": "https://patchwork.libcamera.org/patch/17793/mbox/",
    "series": [
        {
            "id": 3609,
            "url": "https://patchwork.libcamera.org/api/series/3609/?format=api",
            "web_url": "https://patchwork.libcamera.org/project/libcamera/list/?series=3609",
            "date": "2022-11-15T09:07:47",
            "name": "Raspberry Pi AGC digital gain fixes",
            "version": 6,
            "mbox": "https://patchwork.libcamera.org/series/3609/mbox/"
        }
    ],
    "comments": "https://patchwork.libcamera.org/api/patches/17793/comments/",
    "check": "pending",
    "checks": "https://patchwork.libcamera.org/api/patches/17793/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 A93B0BE08B\n\tfor <parsemail@patchwork.libcamera.org>;\n\tTue, 15 Nov 2022 09:08:04 +0000 (UTC)",
            "from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id 6939863088;\n\tTue, 15 Nov 2022 10:08:04 +0100 (CET)",
            "from mail-wm1-x32f.google.com (mail-wm1-x32f.google.com\n\t[IPv6:2a00:1450:4864:20::32f])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id E5C0263089\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tTue, 15 Nov 2022 10:08:01 +0100 (CET)",
            "by mail-wm1-x32f.google.com with SMTP id\n\tay14-20020a05600c1e0e00b003cf6ab34b61so12613467wmb.2\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tTue, 15 Nov 2022 01:08:01 -0800 (PST)",
            "from naush-laptop.localdomain ([93.93.133.154])\n\tby smtp.gmail.com with ESMTPSA id\n\tp25-20020a05600c1d9900b003c64c186206sm15143696wms.16.2022.11.15.01.08.00\n\t(version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256);\n\tTue, 15 Nov 2022 01:08:00 -0800 (PST)"
        ],
        "DKIM-Signature": [
            "v=1; a=rsa-sha256; c=relaxed/simple; d=libcamera.org;\n\ts=mail; t=1668503284;\n\tbh=DByEoimg1lvh8ErW1sv4VviuI8HSuOZoLi0E4p+TQ7w=;\n\th=To:Date:In-Reply-To:References:Subject:List-Id:List-Unsubscribe:\n\tList-Archive:List-Post:List-Help:List-Subscribe:From:Reply-To:\n\tFrom;\n\tb=WmOolv35PZwvDTjRnS+lDSj/mW7UsJ6sD8PJF3A+dl85UbZ8BA738R0fT/q0xIQ7J\n\tiHbUmtEdIQaJxPAB0Eu0x6yQk0MrTwYh1ELYnMy4h/cQyLdIuk/WE9RIny+66HYhI2\n\tpbPxrdVG9j8IqUhxYRugTfGuz5eUE9i13dr1N9I+nSW914BDobP5vj3miXibStapSy\n\tU1swO06bg7mkV2Uxewt3TatFUoSAaA2fo8GyoOkHe5i+hJRkQryujERQG/1gzAk1if\n\tSpydOD8GTtq+E8RLmkO9XOhRobT8GNqMOy8F+AApePKEStF7iQWY3j8c388hXxBd/z\n\tEjKHjUCdarzVA==",
            "v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=raspberrypi.com; s=google;\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=XUEQUArpIfN6duNUMuOAcBmxj99qc641z9vVt3gWhPQ=;\n\tb=tQsaDZRQkPLzjwh9PERIJnFmq3D6177SeqrAXeOna2R9L8Of5n7wvlPTJgKLzVFq4/\n\ttfLJQD/mhkeLM+BoRGxtOEryOrhCqrQQ8HadZYuVk1gEeKa2gY2/n555HBgR3O7UHn9I\n\t+PjARn1qY4+Fl7cIuyI1GniV6Gk2EYpn+fS3Wucnk0tfQ+091sbu3dojykdvMgfX4qHM\n\tO0xq5zmZC0bRdB9ocLKY1IA230dmNN/eC0kM/atNJZN1h48Lrpt8vNDrhtv8THVuV/gr\n\tynY8KEAZQPrMNxiI3FFWsarl67X/Uf2mr08Iv1Jo9cI0ZptCIblKbGb1HWZ9dkSrlZAd\n\taK0Q=="
        ],
        "Authentication-Results": "lancelot.ideasonboard.com; dkim=pass (2048-bit key; \n\tunprotected) header.d=raspberrypi.com\n\theader.i=@raspberrypi.com\n\theader.b=\"tQsaDZRQ\"; dkim-atps=neutral",
        "X-Google-DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20210112;\n\th=content-transfer-encoding:mime-version:references:in-reply-to\n\t:message-id:date:subject:cc:to:from:x-gm-message-state:from:to:cc\n\t:subject:date:message-id:reply-to;\n\tbh=XUEQUArpIfN6duNUMuOAcBmxj99qc641z9vVt3gWhPQ=;\n\tb=hCILExgdQ6cQ+wtrHlbcFf9FtcHsYa9jd8zfAe+brDRLwpRWhzpUIpiepN2DtcnXY9\n\tgufKgp0aZwbBLeAIxLdTu4uw3enRGt3lRC03vyB4gr8MDmIDJ8B3+xKxfY6/b7LV5DQB\n\tfcZ+dFbJkZ1Fvaj10PQFNuCHCqxXbPe2zWQc+vtaD/1uRF5yrs/6zYRUeTXsKow+Z5ls\n\t2Pw8K1Ng2uYLdWbrtuh2cAihtfr+1tUYYlNmrE1fk7FnsjrhlrWj4GLCpP9DJup4RANX\n\tjOa7tuOgKRAdDOH5fQwehivH/mNX6Qu2ECGTj0OfYgOPMrr1FSH+7c5intk4e7wP2C84\n\t43ZQ==",
        "X-Gm-Message-State": "ANoB5pn2PCx77VL9BjUULicqkRvSrQd91ea6APqO/HtqsvS26pKvaK50\n\tfUnPtBnGK+aK9SqK7AVFRY4YC/idSbcLdg==",
        "X-Google-Smtp-Source": "AA0mqf7B0OB0FfOkzvZiW6WnLqhgpFqDCxcTSbc0/MVrj3L2mqS3xZLgyE0tuafzb1PslRmG9HaOJA==",
        "X-Received": "by 2002:a7b:c010:0:b0:3cf:eaf5:77c6 with SMTP id\n\tc16-20020a7bc010000000b003cfeaf577c6mr677213wmb.56.1668503280934; \n\tTue, 15 Nov 2022 01:08:00 -0800 (PST)",
        "To": "libcamera-devel@lists.libcamera.org",
        "Date": "Tue, 15 Nov 2022 09:07:48 +0000",
        "Message-Id": "<20221115090755.2921-2-naush@raspberrypi.com>",
        "X-Mailer": "git-send-email 2.25.1",
        "In-Reply-To": "<20221115090755.2921-1-naush@raspberrypi.com>",
        "References": "<20221115090755.2921-1-naush@raspberrypi.com>",
        "MIME-Version": "1.0",
        "Content-Transfer-Encoding": "8bit",
        "Subject": "[libcamera-devel] [PATCH v6 1/8] pipeline: raspberrypi: Fork\n\tDelayedControls",
        "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>",
        "From": "Naushir Patuck via libcamera-devel\n\t<libcamera-devel@lists.libcamera.org>",
        "Reply-To": "Naushir Patuck <naush@raspberrypi.com>",
        "Errors-To": "libcamera-devel-bounces@lists.libcamera.org",
        "Sender": "\"libcamera-devel\" <libcamera-devel-bounces@lists.libcamera.org>"
    },
    "content": "Fork the libcamera::DelayedControls implementation in the RPi:: namespace, with\nthe intention of updating the API for Raspberry Pi specific features.\n\nSigned-off-by: Naushir Patuck <naush@raspberrypi.com>\n---\n .../pipeline/raspberrypi/delayed_controls.cpp | 291 ++++++++++++++++++\n .../pipeline/raspberrypi/delayed_controls.h   |  84 +++++\n .../pipeline/raspberrypi/meson.build          |   1 +\n 3 files changed, 376 insertions(+)\n create mode 100644 src/libcamera/pipeline/raspberrypi/delayed_controls.cpp\n create mode 100644 src/libcamera/pipeline/raspberrypi/delayed_controls.h",
    "diff": "diff --git a/src/libcamera/pipeline/raspberrypi/delayed_controls.cpp b/src/libcamera/pipeline/raspberrypi/delayed_controls.cpp\nnew file mode 100644\nindex 000000000000..867e3866cc46\n--- /dev/null\n+++ b/src/libcamera/pipeline/raspberrypi/delayed_controls.cpp\n@@ -0,0 +1,291 @@\n+/* SPDX-License-Identifier: LGPL-2.1-or-later */\n+/*\n+ * Copyright (C) 2020, Raspberry Pi Ltd\n+ *\n+ * delayed_controls.cpp - Helper to deal with controls that take effect with a delay\n+ *\n+ * Note: This has been forked from the libcamera core implementation.\n+ */\n+\n+#include \"delayed_controls.h\"\n+\n+#include <libcamera/base/log.h>\n+\n+#include <libcamera/controls.h>\n+\n+#include \"libcamera/internal/v4l2_device.h\"\n+\n+/**\n+ * \\file delayed_controls.h\n+ * \\brief Helper to deal with controls that take effect with a delay\n+ */\n+\n+namespace libcamera {\n+\n+LOG_DEFINE_CATEGORY(RPiDelayedControls)\n+\n+namespace RPi {\n+\n+/**\n+ * \\class DelayedControls\n+ * \\brief Helper to deal with controls that take effect with a delay\n+ *\n+ * Some sensor controls take effect with a delay as the sensor needs time to\n+ * adjust, for example exposure and analog gain. This is a helper class to deal\n+ * with such controls and the intended users are pipeline handlers.\n+ *\n+ * The idea is to extend the concept of the buffer depth of a pipeline the\n+ * application needs to maintain to also cover controls. Just as with buffer\n+ * depth if the application keeps the number of requests queued above the\n+ * control depth the controls are guaranteed to take effect for the correct\n+ * request. The control depth is determined by the control with the greatest\n+ * delay.\n+ */\n+\n+/**\n+ * \\struct DelayedControls::ControlParams\n+ * \\brief Parameters associated with controls handled by the \\a DelayedControls\n+ * helper class\n+ *\n+ * \\var ControlParams::delay\n+ * \\brief Frame delay from setting the control on a sensor device to when it is\n+ * consumed during framing.\n+ *\n+ * \\var ControlParams::priorityWrite\n+ * \\brief Flag to indicate that this control must be applied ahead of, and\n+ * separately from the other controls.\n+ *\n+ * Typically set for the \\a V4L2_CID_VBLANK control so that the device driver\n+ * does not reject \\a V4L2_CID_EXPOSURE control values that may be outside of\n+ * the existing vertical blanking specified bounds, but are within the new\n+ * blanking bounds.\n+ */\n+\n+/**\n+ * \\brief Construct a DelayedControls instance\n+ * \\param[in] device The V4L2 device the controls have to be applied to\n+ * \\param[in] controlParams Map of the numerical V4L2 control ids to their\n+ * associated control parameters.\n+ *\n+ * The control parameters comprise of delays (in frames) and a priority write\n+ * flag. If this flag is set, the relevant control is written separately from,\n+ * and ahead of the rest of the batched controls.\n+ *\n+ * Only controls specified in \\a controlParams are handled. If it's desired to\n+ * mix delayed controls and controls that take effect immediately the immediate\n+ * controls must be listed in the \\a controlParams map with a delay value of 0.\n+ */\n+DelayedControls::DelayedControls(V4L2Device *device,\n+\t\t\t\t const std::unordered_map<uint32_t, ControlParams> &controlParams)\n+\t: device_(device), maxDelay_(0)\n+{\n+\tconst ControlInfoMap &controls = device_->controls();\n+\n+\t/*\n+\t * Create a map of control ids to delays for controls exposed by the\n+\t * device.\n+\t */\n+\tfor (auto const &param : controlParams) {\n+\t\tauto it = controls.find(param.first);\n+\t\tif (it == controls.end()) {\n+\t\t\tLOG(RPiDelayedControls, Error)\n+\t\t\t\t<< \"Delay request for control id \"\n+\t\t\t\t<< utils::hex(param.first)\n+\t\t\t\t<< \" but control is not exposed by device \"\n+\t\t\t\t<< device_->deviceNode();\n+\t\t\tcontinue;\n+\t\t}\n+\n+\t\tconst ControlId *id = it->first;\n+\n+\t\tcontrolParams_[id] = param.second;\n+\n+\t\tLOG(RPiDelayedControls, Debug)\n+\t\t\t<< \"Set a delay of \" << controlParams_[id].delay\n+\t\t\t<< \" and priority write flag \" << controlParams_[id].priorityWrite\n+\t\t\t<< \" for \" << id->name();\n+\n+\t\tmaxDelay_ = std::max(maxDelay_, controlParams_[id].delay);\n+\t}\n+\n+\treset();\n+}\n+\n+/**\n+ * \\brief Reset state machine\n+ *\n+ * Resets the state machine to a starting position based on control values\n+ * retrieved from the device.\n+ */\n+void DelayedControls::reset()\n+{\n+\tqueueCount_ = 1;\n+\twriteCount_ = 0;\n+\n+\t/* Retrieve control as reported by the device. */\n+\tstd::vector<uint32_t> ids;\n+\tfor (auto const &param : controlParams_)\n+\t\tids.push_back(param.first->id());\n+\n+\tControlList controls = device_->getControls(ids);\n+\n+\t/* Seed the control queue with the controls reported by the device. */\n+\tvalues_.clear();\n+\tfor (const auto &ctrl : controls) {\n+\t\tconst ControlId *id = device_->controls().idmap().at(ctrl.first);\n+\t\t/*\n+\t\t * Do not mark this control value as updated, it does not need\n+\t\t * to be written to to device on startup.\n+\t\t */\n+\t\tvalues_[id][0] = Info(ctrl.second, false);\n+\t}\n+}\n+\n+/**\n+ * \\brief Push a set of controls on the queue\n+ * \\param[in] controls List of controls to add to the device queue\n+ *\n+ * Push a set of controls to the control queue. This increases the control queue\n+ * depth by one.\n+ *\n+ * \\returns true if \\a controls are accepted, or false otherwise\n+ */\n+bool DelayedControls::push(const ControlList &controls)\n+{\n+\t/* Copy state from previous frame. */\n+\tfor (auto &ctrl : values_) {\n+\t\tInfo &info = ctrl.second[queueCount_];\n+\t\tinfo = values_[ctrl.first][queueCount_ - 1];\n+\t\tinfo.updated = false;\n+\t}\n+\n+\t/* Update with new controls. */\n+\tconst ControlIdMap &idmap = device_->controls().idmap();\n+\tfor (const auto &control : controls) {\n+\t\tconst auto &it = idmap.find(control.first);\n+\t\tif (it == idmap.end()) {\n+\t\t\tLOG(RPiDelayedControls, Warning)\n+\t\t\t\t<< \"Unknown control \" << control.first;\n+\t\t\treturn false;\n+\t\t}\n+\n+\t\tconst ControlId *id = it->second;\n+\n+\t\tif (controlParams_.find(id) == controlParams_.end())\n+\t\t\treturn false;\n+\n+\t\tInfo &info = values_[id][queueCount_];\n+\n+\t\tinfo = Info(control.second);\n+\n+\t\tLOG(RPiDelayedControls, Debug)\n+\t\t\t<< \"Queuing \" << id->name()\n+\t\t\t<< \" to \" << info.toString()\n+\t\t\t<< \" at index \" << queueCount_;\n+\t}\n+\n+\tqueueCount_++;\n+\n+\treturn true;\n+}\n+\n+/**\n+ * \\brief Read back controls in effect at a sequence number\n+ * \\param[in] sequence The sequence number to get controls for\n+ *\n+ * Read back what controls where in effect at a specific sequence number. The\n+ * history is a ring buffer of 16 entries where new and old values coexist. It's\n+ * the callers responsibility to not read too old sequence numbers that have been\n+ * pushed out of the history.\n+ *\n+ * Historic values are evicted by pushing new values onto the queue using\n+ * push(). The max history from the current sequence number that yields valid\n+ * values are thus 16 minus number of controls pushed.\n+ *\n+ * \\return The controls at \\a sequence number\n+ */\n+ControlList DelayedControls::get(uint32_t sequence)\n+{\n+\tunsigned int index = std::max<int>(0, sequence - maxDelay_);\n+\n+\tControlList out(device_->controls());\n+\tfor (const auto &ctrl : values_) {\n+\t\tconst ControlId *id = ctrl.first;\n+\t\tconst Info &info = ctrl.second[index];\n+\n+\t\tout.set(id->id(), info);\n+\n+\t\tLOG(RPiDelayedControls, Debug)\n+\t\t\t<< \"Reading \" << id->name()\n+\t\t\t<< \" to \" << info.toString()\n+\t\t\t<< \" at index \" << index;\n+\t}\n+\n+\treturn out;\n+}\n+\n+/**\n+ * \\brief Inform DelayedControls of the start of a new frame\n+ * \\param[in] sequence Sequence number of the frame that started\n+ *\n+ * Inform the state machine that a new frame has started and of its sequence\n+ * number. Any user of these helpers is responsible to inform the helper about\n+ * the start of any frame. This can be connected with ease to the start of a\n+ * exposure (SOE) V4L2 event.\n+ */\n+void DelayedControls::applyControls(uint32_t sequence)\n+{\n+\tLOG(RPiDelayedControls, Debug) << \"frame \" << sequence << \" started\";\n+\n+\t/*\n+\t * Create control list peeking ahead in the value queue to ensure\n+\t * values are set in time to satisfy the sensor delay.\n+\t */\n+\tControlList out(device_->controls());\n+\tfor (auto &ctrl : values_) {\n+\t\tconst ControlId *id = ctrl.first;\n+\t\tunsigned int delayDiff = maxDelay_ - controlParams_[id].delay;\n+\t\tunsigned int index = std::max<int>(0, writeCount_ - delayDiff);\n+\t\tInfo &info = ctrl.second[index];\n+\n+\t\tif (info.updated) {\n+\t\t\tif (controlParams_[id].priorityWrite) {\n+\t\t\t\t/*\n+\t\t\t\t * This control must be written now, it could\n+\t\t\t\t * affect validity of the other controls.\n+\t\t\t\t */\n+\t\t\t\tControlList priority(device_->controls());\n+\t\t\t\tpriority.set(id->id(), info);\n+\t\t\t\tdevice_->setControls(&priority);\n+\t\t\t} else {\n+\t\t\t\t/*\n+\t\t\t\t * Batch up the list of controls and write them\n+\t\t\t\t * at the end of the function.\n+\t\t\t\t */\n+\t\t\t\tout.set(id->id(), info);\n+\t\t\t}\n+\n+\t\t\tLOG(RPiDelayedControls, Debug)\n+\t\t\t\t<< \"Setting \" << id->name()\n+\t\t\t\t<< \" to \" << info.toString()\n+\t\t\t\t<< \" at index \" << index;\n+\n+\t\t\t/* Done with this update, so mark as completed. */\n+\t\t\tinfo.updated = false;\n+\t\t}\n+\t}\n+\n+\twriteCount_ = sequence + 1;\n+\n+\twhile (writeCount_ > queueCount_) {\n+\t\tLOG(RPiDelayedControls, Debug)\n+\t\t\t<< \"Queue is empty, auto queue no-op.\";\n+\t\tpush({});\n+\t}\n+\n+\tdevice_->setControls(&out);\n+}\n+\n+} /* namespace RPi */\n+\n+} /* namespace libcamera */\ndiff --git a/src/libcamera/pipeline/raspberrypi/delayed_controls.h b/src/libcamera/pipeline/raspberrypi/delayed_controls.h\nnew file mode 100644\nindex 000000000000..f7f246482968\n--- /dev/null\n+++ b/src/libcamera/pipeline/raspberrypi/delayed_controls.h\n@@ -0,0 +1,84 @@\n+/* SPDX-License-Identifier: LGPL-2.1-or-later */\n+/*\n+ * Copyright (C) 2020, Raspberry Pi Ltd\n+ *\n+ * delayed_controls.h - Helper to deal with controls that take effect with a delay\n+ *\n+ * Note: This has been forked from the libcamera core implementation.\n+ */\n+\n+#pragma once\n+\n+#include <stdint.h>\n+#include <unordered_map>\n+\n+#include <libcamera/controls.h>\n+\n+namespace libcamera {\n+\n+class V4L2Device;\n+\n+namespace RPi {\n+\n+class DelayedControls\n+{\n+public:\n+\tstruct ControlParams {\n+\t\tunsigned int delay;\n+\t\tbool priorityWrite;\n+\t};\n+\n+\tDelayedControls(V4L2Device *device,\n+\t\t\tconst std::unordered_map<uint32_t, ControlParams> &controlParams);\n+\n+\tvoid reset();\n+\n+\tbool push(const ControlList &controls);\n+\tControlList get(uint32_t sequence);\n+\n+\tvoid applyControls(uint32_t sequence);\n+\n+private:\n+\tclass Info : public ControlValue\n+\t{\n+\tpublic:\n+\t\tInfo()\n+\t\t\t: updated(false)\n+\t\t{\n+\t\t}\n+\n+\t\tInfo(const ControlValue &v, bool updated_ = true)\n+\t\t\t: ControlValue(v), updated(updated_)\n+\t\t{\n+\t\t}\n+\n+\t\tbool updated;\n+\t};\n+\n+\tstatic constexpr int listSize = 16;\n+\tclass ControlRingBuffer : public std::array<Info, listSize>\n+\t{\n+\tpublic:\n+\t\tInfo &operator[](unsigned int index)\n+\t\t{\n+\t\t\treturn std::array<Info, listSize>::operator[](index % listSize);\n+\t\t}\n+\n+\t\tconst Info &operator[](unsigned int index) const\n+\t\t{\n+\t\t\treturn std::array<Info, listSize>::operator[](index % listSize);\n+\t\t}\n+\t};\n+\n+\tV4L2Device *device_;\n+\tstd::unordered_map<const ControlId *, ControlParams> controlParams_;\n+\tunsigned int maxDelay_;\n+\n+\tuint32_t queueCount_;\n+\tuint32_t writeCount_;\n+\tstd::unordered_map<const ControlId *, ControlRingBuffer> values_;\n+};\n+\n+} /* namespace RPi */\n+\n+} /* namespace libcamera */\ndiff --git a/src/libcamera/pipeline/raspberrypi/meson.build b/src/libcamera/pipeline/raspberrypi/meson.build\nindex f1a2f5ee72c2..6064a3f00122 100644\n--- a/src/libcamera/pipeline/raspberrypi/meson.build\n+++ b/src/libcamera/pipeline/raspberrypi/meson.build\n@@ -1,6 +1,7 @@\n # SPDX-License-Identifier: CC0-1.0\n \n libcamera_sources += files([\n+    'delayed_controls.cpp',\n     'dma_heaps.cpp',\n     'raspberrypi.cpp',\n     'rpi_stream.cpp',\n",
    "prefixes": [
        "libcamera-devel",
        "v6",
        "1/8"
    ]
}