{"id":15099,"url":"https://patchwork.libcamera.org/api/1.1/patches/15099/?format=json","web_url":"https://patchwork.libcamera.org/patch/15099/","project":{"id":1,"url":"https://patchwork.libcamera.org/api/1.1/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":"<CAJ+kdVH_0cE_Mj-P-P5-+rYeQKHJM2TTSN2wT904GFi6yN=4qQ@mail.gmail.com>","date":"2021-12-09T11:59:04","name":"[libcamera-devel] libcamera: pipeline: simple: Add support for controls","commit_ref":null,"pull_url":null,"state":"new","archived":false,"hash":"13a42b2db1b607ef827644a8269b1367fe0a9efc","submitter":{"id":108,"url":"https://patchwork.libcamera.org/api/1.1/people/108/?format=json","name":"Benjamin Schaaf","email":"ben.schaaf@gmail.com"},"delegate":null,"mbox":"https://patchwork.libcamera.org/patch/15099/mbox/","series":[{"id":2831,"url":"https://patchwork.libcamera.org/api/1.1/series/2831/?format=json","web_url":"https://patchwork.libcamera.org/project/libcamera/list/?series=2831","date":"2021-12-09T11:59:04","name":"[libcamera-devel] libcamera: pipeline: simple: Add support for controls","version":1,"mbox":"https://patchwork.libcamera.org/series/2831/mbox/"}],"comments":"https://patchwork.libcamera.org/api/patches/15099/comments/","check":"pending","checks":"https://patchwork.libcamera.org/api/patches/15099/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 17A20BDB13\n\tfor <parsemail@patchwork.libcamera.org>;\n\tThu,  9 Dec 2021 11:59:19 +0000 (UTC)","from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id C48976086A;\n\tThu,  9 Dec 2021 12:59:18 +0100 (CET)","from mail-yb1-xb32.google.com (mail-yb1-xb32.google.com\n\t[IPv6:2607:f8b0:4864:20::b32])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id AADCB607DE\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tThu,  9 Dec 2021 12:59:16 +0100 (CET)","by mail-yb1-xb32.google.com with SMTP id 131so13087821ybc.7\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tThu, 09 Dec 2021 03:59:16 -0800 (PST)"],"Authentication-Results":"lancelot.ideasonboard.com;\n\tdkim=fail reason=\"signature verification failed\" (2048-bit key;\n\tunprotected) header.d=gmail.com header.i=@gmail.com\n\theader.b=\"O8lqURTN\"; dkim-atps=neutral","DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20210112;\n\th=mime-version:from:date:message-id:subject:to;\n\tbh=wDA4qvP4DTeS/Vf2iPE3WC/kUaCc6Dp4WRLtPeQr8iM=;\n\tb=O8lqURTNhWmLWqSSoIxd1nvllfzbBBY4mibzZ0SXeeA/9z5jaqnaa5Oa46ksH+33oP\n\tuysUAwQgBnxXbcJxKQM6TwFmGsI3zejR+TB8q9NnLFTJljNrFubkge99QbnSwjVR3ysd\n\tldW2AuJk3QTNW4JhLUFokCNzNW90G2VhNgvFE6nqdXR22wiHacXAbDTGvhMK6wxagDAv\n\txcGEMi0eTcY69Cu2EM2MYTOBApaDugJHS98Uzyff3wtdYfrYPHP1ALGIolkPsqzlmMl9\n\thhJtAqy3O1snXEnBZEYLecL6/f3BOojTnGDnzVsabZbGx7rCfeWmLG2iuo44UzvcyGNx\n\t5yjg==","X-Google-DKIM-Signature":"v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20210112;\n\th=x-gm-message-state:mime-version:from:date:message-id:subject:to;\n\tbh=wDA4qvP4DTeS/Vf2iPE3WC/kUaCc6Dp4WRLtPeQr8iM=;\n\tb=zVBDfRsCGFXXkmCbn0l4T8YLX3ydPEKuIpWgIjBrOxuWWyJUDVFKUIXXZinQv7Q1re\n\tnLLhv1Zc+3U803CNq1wM7EHOiU+JAU9wL+YWgiAvPjWj2r0aH9Syihd4PvR3Q9ham5Jr\n\tqi2HVc7OofJiQe8DZxz7C4liL5pQ8hicyKDVGqgs6W0vkJeu5NBZ3X1bQHDnQvU6y6AU\n\tVGAN2mkJdQ//1YxeDUdng+DPW/3WikJUJuDDPPIGtpiYwRXVI7tCi9Tp7XOJLhAtZF8l\n\txfGz5DK5+n2FH+rDKJ07G9/AbIWAfYrK6RT64iF5lTIiC0U6ZajKmDyzt4wC7pzKE4l6\n\t8R1A==","X-Gm-Message-State":"AOAM533I6U8SGujjyWN0/8f/KQrdyYMq3Jrrt98QRZ2iIfk0WAda9P0S\n\tA0EnmuzGyh+JO0Gr1K1sDMUYQps6ox2pBByb0Zq62q5zaKqvIg==","X-Google-Smtp-Source":"ABdhPJyUPtM+LJL2Y4gqMwXTQumBu4TUq5KabAC6J8z8fBzgM/ODDKrR0sBv2jLKXs77iCjgCrTTw6Kr7R6/2OkFyIg=","X-Received":"by 2002:a25:9bc9:: with SMTP id w9mr5665777ybo.398.1639051155168;\n\tThu, 09 Dec 2021 03:59:15 -0800 (PST)","MIME-Version":"1.0","From":"Benjamin Schaaf <ben.schaaf@gmail.com>","Date":"Thu, 9 Dec 2021 22:59:04 +1100","Message-ID":"<CAJ+kdVH_0cE_Mj-P-P5-+rYeQKHJM2TTSN2wT904GFi6yN=4qQ@mail.gmail.com>","To":"libcamera-devel@lists.libcamera.org","Content-Type":"text/plain; charset=\"UTF-8\"","Subject":"[libcamera-devel] [PATCH] libcamera: pipeline: simple: Add support\n\tfor controls","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":"Controls and control info are translated between libcamera and V4L2\ninside the simple pipeline. Request controls are applied when a request\nis next to be completed.\n\nThis also adds some additional draft controls needed for the PinePhone.\n\nBug: https://bugs.libcamera.org/show_bug.cgi?id=98\nSigned-off-by: Benjamin Schaaf <ben.schaaf@gmail.com>\n---\n src/libcamera/control_ids.yaml             |  24 ++\n src/libcamera/pipeline/simple/controls.cpp | 242 +++++++++++++++++++++\n src/libcamera/pipeline/simple/controls.h   |  30 +++\n src/libcamera/pipeline/simple/meson.build  |   1 +\n src/libcamera/pipeline/simple/simple.cpp   |  30 ++-\n src/libcamera/v4l2_device.cpp              |  12 +-\n 6 files changed, 337 insertions(+), 2 deletions(-)\n create mode 100644 src/libcamera/pipeline/simple/controls.cpp\n create mode 100644 src/libcamera/pipeline/simple/controls.h\n\n         return v4l2MenuControlInfo(ctrl);","diff":"diff --git a/src/libcamera/control_ids.yaml b/src/libcamera/control_ids.yaml\nindex 9d4638ae..2af230c3 100644\n--- a/src/libcamera/control_ids.yaml\n+++ b/src/libcamera/control_ids.yaml\n@@ -406,6 +406,30 @@ controls:\n             The camera will cancel any active or completed metering sequence.\n             The AE algorithm is reset to its initial state.\n\n+  - AutoGain:\n+      type: bool\n+      draft: true\n+      description: |\n+       Control for Automatic Gain. Currently identical to V4L2_CID_AUTOGAIN.\n+\n+  - AfEnabled:\n+      type: bool\n+      draft: true\n+      description: |\n+       Control for AF. Currently identical to V4L2_CID_FOCUS_AUTO.\n+\n+  - AfStart:\n+      type: void\n+      draft: true\n+      description: |\n+       Control for AF. Currently identical to V4L2_CID_AUTO_FOCUS_START.\n+\n+  - AfStop:\n+      type: void\n+      draft: true\n+      description: |\n+       Control for AF. Currently identical to V4L2_CID_AUTO_FOCUS_END.\n+\n   - AfTrigger:\n       type: int32_t\n       draft: true\ndiff --git a/src/libcamera/pipeline/simple/controls.cpp\nb/src/libcamera/pipeline/simple/controls.cpp\nnew file mode 100644\nindex 00000000..2d3cfc62\n--- /dev/null\n+++ b/src/libcamera/pipeline/simple/controls.cpp\n@@ -0,0 +1,242 @@\n+/* SPDX-License-Identifier: LGPL-2.1-or-later */\n+/*\n+ * Copyright (C) 2019, Benjamin Schaaf\n+ *\n+ * controls.cpp - Simple pipeline control conversion\n+ */\n+\n+#include \"controls.h\"\n+\n+#include <linux/v4l2-controls.h>\n+\n+#include <libcamera/base/log.h>\n+\n+#include <libcamera/control_ids.h>\n+\n+namespace libcamera {\n+\n+LOG_DECLARE_CATEGORY(SimplePipeline)\n+\n+/*\n+ * These controls can be directly mapped between libcamera and V4L2 without\n+ * doing any conversion to the ControlValue.\n+ */\n+namespace {\n+std::unordered_map<unsigned int, unsigned int> controlsToV4L2 = {\n+    { controls::AUTO_GAIN, V4L2_CID_AUTOGAIN },\n+    { controls::AF_ENABLED, V4L2_CID_FOCUS_AUTO },\n+    { controls::AF_START, V4L2_CID_AUTO_FOCUS_START },\n+    { controls::AF_STOP, V4L2_CID_AUTO_FOCUS_STOP },\n+    { controls::AE_ENABLE, V4L2_CID_EXPOSURE_AUTO },\n+    { controls::EXPOSURE_VALUE, V4L2_CID_EXPOSURE },\n+    { controls::DIGITAL_GAIN, V4L2_CID_GAIN },\n+    { controls::ANALOGUE_GAIN, V4L2_CID_ANALOGUE_GAIN },\n+    { controls::AF_STATE, V4L2_CID_AUTO_FOCUS_STATUS },\n+};\n+std::unordered_map<unsigned int, unsigned int> controlsFromV4L2;\n+}\n+\n+/**\n+ * \\brief Convert from a libcamera control to a V4L2 control.\n+ *\n+ * Can optionally convert the libcamera control and/or a set of libcamera\n+ * control values to their V4L2 equivalents.\n+ */\n+bool simpleControlToV4L2(unsigned int control,\n+             unsigned int *v4l2_control,\n+             const ControlValue *control_values,\n+             ControlValue *v4l2_values,\n+             size_t num_values)\n+{\n+    // Convert controls\n+    if (v4l2_control) {\n+        auto it = controlsToV4L2.find(control);\n+        if (it == controlsToV4L2.end())\n+            return false;\n+\n+        *v4l2_control = it->second;\n+    }\n+\n+    // Convert values\n+    if (num_values == 0)\n+        return true;\n+\n+    switch (control) {\n+    case controls::AE_ENABLE:\n+        for (size_t i = 0; i < num_values; ++i)\n+            v4l2_values[i] =\nControlValue((int32_t)(control_values[i].get<bool>() ?\nV4L2_EXPOSURE_AUTO : V4L2_EXPOSURE_MANUAL));\n+        return true;\n+    case controls::EXPOSURE_VALUE:\n+    case controls::DIGITAL_GAIN:\n+    case controls::ANALOGUE_GAIN:\n+        for (size_t i = 0; i < num_values; ++i)\n+            v4l2_values[i] =\nControlValue((int32_t)control_values[i].get<float>());\n+        return true;\n+    // Read only\n+    case controls::AF_STATE:\n+        return false;\n+    default:\n+        for (size_t i = 0; i < num_values; ++i)\n+            v4l2_values[i] = control_values[i];\n+        return true;\n+    }\n+}\n+\n+/**\n+ * \\brief Convert from a V4L2 control to a libcamera control.\n+ *\n+ * Can optionally convert the V4L2 control and/or a set of V4L2 control values\n+ * to their libcamera equivalents.\n+ */\n+bool simpleControlFromV4L2(unsigned int v4l2_control,\n+               unsigned int *control,\n+               const ControlValue *v4l2_values,\n+               ControlValue *control_values,\n+               size_t num_values)\n+{\n+    // Initialize the inverse of controlsToV4L2\n+    if (controlsFromV4L2.empty()) {\n+        for (const auto &v : controlsToV4L2) {\n+            controlsFromV4L2[v.second] = v.first;\n+        }\n+    }\n+\n+    // Convert control\n+    if (control) {\n+        auto it = controlsFromV4L2.find(v4l2_control);\n+        if (it == controlsFromV4L2.end())\n+            return false;\n+\n+        *control = it->second;\n+    }\n+\n+    // Convert values\n+    if (num_values == 0)\n+        return true;\n+\n+    switch (v4l2_control) {\n+    case V4L2_CID_EXPOSURE_AUTO:\n+        for (size_t i = 0; i < num_values; ++i)\n+            control_values[i] =\nControlValue(v4l2_values[i].get<int32_t>() == V4L2_EXPOSURE_AUTO);\n+        return true;\n+    case V4L2_CID_EXPOSURE:\n+    case V4L2_CID_GAIN:\n+    case V4L2_CID_ANALOGUE_GAIN:\n+        for (size_t i = 0; i < num_values; ++i)\n+            control_values[i] =\nControlValue((float)v4l2_values[i].get<int32_t>());\n+        return true;\n+    case V4L2_CID_AUTO_FOCUS_STATUS:\n+        for (size_t i = 0; i < num_values; ++i) {\n+            switch (v4l2_values[i].get<int32_t>()) {\n+            case V4L2_AUTO_FOCUS_STATUS_IDLE:\n+                control_values[i] =\nControlValue((int32_t)controls::draft::AfStateInactive);\n+                break;\n+            case V4L2_AUTO_FOCUS_STATUS_BUSY:\n+                control_values[i] =\nControlValue((int32_t)controls::draft::AfStateActiveScan);\n+                break;\n+            case V4L2_AUTO_FOCUS_STATUS_REACHED:\n+                control_values[i] =\nControlValue((int32_t)controls::draft::AfStatePassiveFocused);\n+                break;\n+            case V4L2_AUTO_FOCUS_STATUS_FAILED:\n+                control_values[i] =\nControlValue((int32_t)controls::draft::AfStatePassiveUnfocused);\n+                break;\n+            default:\n+                LOG(SimplePipeline, Error)\n+                    << \"AUTO_FOCUS_STATUS has invalid value: \"\n+                    << utils::hex(v4l2_values[i].get<int32_t>());\n+                /*TODO: Log Error*/\n+                return false;\n+            }\n+        }\n+        return true;\n+    default:\n+        for (size_t i = 0; i < num_values; ++i)\n+            control_values[i] = v4l2_values[i];\n+        return true;\n+    }\n+}\n+\n+/**\n+ * \\brief Convert a ControlInfoMap from V4L2 to libcamera.\n+ *\n+ * Converts both the control identifiers as well as all values.\n+ */\n+ControlInfoMap simpleControlInfoFromV4L2(const ControlInfoMap &v4l2_info_map)\n+{\n+    ControlInfoMap::Map info_map;\n+\n+    for (const auto &pair : v4l2_info_map) {\n+        unsigned int v4l2_control = pair.first->id();\n+        const ControlInfo &v4l2_info = pair.second;\n+\n+        unsigned int control;\n+        ControlValue def;\n+        if (!simpleControlFromV4L2(v4l2_control, &control,\n&v4l2_info.def(), &def, 1))\n+            continue;\n+\n+        const ControlId *control_id = controls::controls.at(control);\n+\n+        // ControlInfo has either a list of values or a minimum and\n+        // maximum. This includes controls that have no values or are\n+        // booleans.\n+        ControlInfo info;\n+        if (v4l2_info.values().empty()) {\n+            ControlValue min, max;\n+            simpleControlFromV4L2(v4l2_control, nullptr,\n&v4l2_info.min(), &min, 1);\n+            simpleControlFromV4L2(v4l2_control, nullptr,\n&v4l2_info.max(), &max, 1);\n+            info = ControlInfo(std::move(min), std::move(max), std::move(def));\n+        } else {\n+            std::vector<ControlValue> values;\n+            values.resize(v4l2_info.values().size());\n+            simpleControlFromV4L2(v4l2_control, nullptr,\nv4l2_info.values().data(), values.data(), values.size());\n+            info = ControlInfo(std::move(values), std::move(def));\n+        }\n+        info_map.emplace(control_id, std::move(info));\n+    }\n+\n+    return ControlInfoMap(std::move(info_map), controls::controls);\n+}\n+\n+/**\n+ * \\brief Convert a control list from libcamera to V4L2.\n+ */\n+ControlList simpleControlListToV4L2(const ControlList &controls)\n+{\n+    ControlList v4l2_controls;\n+    for (const auto &pair : controls) {\n+        unsigned int control = pair.first;\n+        const ControlValue &value = pair.second;\n+\n+        unsigned int v4l2_control;\n+        ControlValue v4l2_value;\n+        if (!simpleControlToV4L2(control, &v4l2_control, &value,\n&v4l2_value, 1)) {\n+            LOG(SimplePipeline, Warning)\n+                << \"Control \" << utils::hex(control)\n+                << \" does not have a V4L2 equivalent\";\n+            continue;\n+        }\n+\n+        v4l2_controls.set(v4l2_control, v4l2_value);\n+    }\n+    return v4l2_controls;\n+}\n+\n+/**\n+ * \\brief Convert a control list from V4L2 to libcamera.\n+ */\n+ControlList simpleControlListFromV4L2(const ControlList &v4l2_controls)\n+{\n+    ControlList controls;\n+    for (const auto &pair : v4l2_controls) {\n+        unsigned int v4l2_control = pair.first;\n+        const ControlValue &v4l2_value = pair.second;\n+\n+        unsigned int control;\n+        ControlValue value;\n+        if (simpleControlFromV4L2(v4l2_control, &control,\n&v4l2_value, &value, 1))\n+            controls.set(control, value);\n+    }\n+    return controls;\n+}\n+\n+} /* namespace libcamera */\ndiff --git a/src/libcamera/pipeline/simple/controls.h\nb/src/libcamera/pipeline/simple/controls.h\nnew file mode 100644\nindex 00000000..cdbcaed1\n--- /dev/null\n+++ b/src/libcamera/pipeline/simple/controls.h\n@@ -0,0 +1,30 @@\n+/* SPDX-License-Identifier: LGPL-2.1-or-later */\n+/*\n+ * Copyright (C) 2019, Benjamin Schaaf\n+ *\n+ * controls.h - Simple pipeline control conversion\n+ */\n+\n+#pragma once\n+\n+#include <libcamera/controls.h>\n+\n+namespace libcamera {\n+\n+bool simpleControlToV4L2(unsigned int control,\n+             unsigned int *v4l2_control,\n+             const ControlValue *control_values,\n+             ControlValue *v4l2_values,\n+             size_t num_values);\n+bool simpleControlFromV4L2(unsigned int v4l2_control,\n+               unsigned int *control,\n+               const ControlValue *v4l2_values,\n+               ControlValue *control_values,\n+               size_t num_values);\n+\n+ControlInfoMap simpleControlInfoFromV4L2(const ControlInfoMap &v4l2_info);\n+\n+ControlList simpleControlListToV4L2(const ControlList &controls);\n+ControlList simpleControlListFromV4L2(const ControlList &controls);\n+\n+} /* namespace libcamera */\ndiff --git a/src/libcamera/pipeline/simple/meson.build\nb/src/libcamera/pipeline/simple/meson.build\nindex 9c99b32f..0c60d65a 100644\n--- a/src/libcamera/pipeline/simple/meson.build\n+++ b/src/libcamera/pipeline/simple/meson.build\n@@ -1,6 +1,7 @@\n # SPDX-License-Identifier: CC0-1.0\n\n libcamera_sources += files([\n+    'controls.cpp',\n     'converter.cpp',\n     'simple.cpp',\n ])\ndiff --git a/src/libcamera/pipeline/simple/simple.cpp\nb/src/libcamera/pipeline/simple/simple.cpp\nindex a597e27f..1717a1a7 100644\n--- a/src/libcamera/pipeline/simple/simple.cpp\n+++ b/src/libcamera/pipeline/simple/simple.cpp\n@@ -36,6 +36,7 @@\n #include \"libcamera/internal/v4l2_subdevice.h\"\n #include \"libcamera/internal/v4l2_videodevice.h\"\n\n+#include \"controls.h\"\n #include \"converter.h\"\n\n namespace libcamera {\n@@ -181,6 +182,7 @@ public:\n     int setupLinks();\n     int setupFormats(V4L2SubdeviceFormat *format,\n              V4L2Subdevice::Whence whence);\n+    int setRequestControls(Request *request);\n     void bufferReady(FrameBuffer *buffer);\n\n     unsigned int streamIndex(const Stream *stream) const\n@@ -519,7 +521,8 @@ int SimpleCameraData::init()\n             formats_[fmt] = &config;\n     }\n\n-    properties_ = sensor_->properties();\n+    properties_ = simpleControlListFromV4L2(sensor_->properties());\n+    controlInfo_ = simpleControlInfoFromV4L2(sensor_->controls());\n\n     return 0;\n }\n@@ -624,6 +627,23 @@ int\nSimpleCameraData::setupFormats(V4L2SubdeviceFormat *format,\n     return 0;\n }\n\n+int SimpleCameraData::setRequestControls(Request *request)\n+{\n+    SimplePipelineHandler *pipe = SimpleCameraData::pipe();\n+\n+    // Apply controls only to one entity. If there's a subdevice use that.\n+    V4L2Device *control_device = video_;\n+    for (const SimpleCameraData::Entity &e : entities_) {\n+        V4L2Subdevice *subdev = pipe->subdev(e.entity);\n+        if (subdev) {\n+            control_device = subdev;\n+        }\n+    }\n+\n+    ControlList controls = simpleControlListToV4L2(request->controls());\n+    return control_device->setControls(&controls);\n+}\n+\n void SimpleCameraData::bufferReady(FrameBuffer *buffer)\n {\n     SimplePipelineHandler *pipe = SimpleCameraData::pipe();\n@@ -666,6 +686,10 @@ void SimpleCameraData::bufferReady(FrameBuffer *buffer)\n         return;\n     }\n\n+    // Set the controls for the next queued request\n+    if (!queuedRequests_.empty())\n+        setRequestControls(queuedRequests_.front());\n+\n     /*\n      * Record the sensor's timestamp in the request metadata. The request\n      * needs to be obtained from the user-facing buffer, as internal\n@@ -1033,6 +1057,10 @@ int SimplePipelineHandler::start(Camera\n*camera, [[maybe_unused]] const ControlL\n         return ret;\n     }\n\n+    // Apply controls from first request\n+    if (!data->queuedRequests_.empty())\n+        data->setRequestControls(data->queuedRequests_.front());\n+\n     if (data->useConverter_) {\n         ret = data->converter_->start();\n         if (ret < 0) {\ndiff --git a/src/libcamera/v4l2_device.cpp b/src/libcamera/v4l2_device.cpp\nindex 39f36009..7a3b56a2 100644\n--- a/src/libcamera/v4l2_device.cpp\n+++ b/src/libcamera/v4l2_device.cpp\n@@ -293,6 +293,13 @@ int V4L2Device::setControls(ControlList *ctrls)\n         /* Set the v4l2_ext_control value for the write operation. */\n         ControlValue &value = ctrl->second;\n         switch (iter->first->type()) {\n+        case ControlTypeBool:\n+            v4l2Ctrl.value64 = value.get<bool>();\n+            break;\n+\n+        case ControlTypeNone:\n+            break;\n+\n         case ControlTypeInteger64:\n             v4l2Ctrl.value64 = value.get<int64_t>();\n             break;\n@@ -476,7 +483,6 @@ ControlType V4L2Device::v4l2CtrlType(uint32_t ctrlType)\n         return ControlTypeInteger64;\n\n     case V4L2_CTRL_TYPE_MENU:\n-    case V4L2_CTRL_TYPE_BUTTON:\n     case V4L2_CTRL_TYPE_BITMASK:\n     case V4L2_CTRL_TYPE_INTEGER_MENU:\n         /*\n@@ -485,6 +491,7 @@ ControlType V4L2Device::v4l2CtrlType(uint32_t ctrlType)\n          */\n         return ControlTypeInteger32;\n\n+    case V4L2_CTRL_TYPE_BUTTON:\n     default:\n         return ControlTypeNone;\n     }\n@@ -527,6 +534,9 @@ ControlInfo V4L2Device::v4l2ControlInfo(const\nv4l2_query_ext_ctrl &ctrl)\n                    static_cast<int64_t>(ctrl.maximum),\n                    static_cast<int64_t>(ctrl.default_value));\n\n+    case V4L2_CTRL_TYPE_BUTTON:\n+        return ControlInfo(ControlValue(), ControlValue(), ControlValue());\n+\n     case V4L2_CTRL_TYPE_INTEGER_MENU:\n     case V4L2_CTRL_TYPE_MENU:\n","prefixes":["libcamera-devel"]}