Show a patch.

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

{
    "id": 23609,
    "url": "https://patchwork.libcamera.org/api/patches/23609/?format=api",
    "web_url": "https://patchwork.libcamera.org/patch/23609/",
    "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": "<20250620124452.557855-2-naush@raspberrypi.com>",
    "date": "2025-06-20T12:42:22",
    "name": "[v1,1/8] ipa: rpi: Defer initialising AF LensPosition ControlInfo and value",
    "commit_ref": null,
    "pull_url": null,
    "state": "accepted",
    "archived": false,
    "hash": "9e1406d8ea3288ed25144209f359e725aba53bb3",
    "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/23609/mbox/",
    "series": [
        {
            "id": 5235,
            "url": "https://patchwork.libcamera.org/api/series/5235/?format=api",
            "web_url": "https://patchwork.libcamera.org/project/libcamera/list/?series=5235",
            "date": "2025-06-20T12:42:21",
            "name": "Raspberry Pi: AF improvements",
            "version": 1,
            "mbox": "https://patchwork.libcamera.org/series/5235/mbox/"
        }
    ],
    "comments": "https://patchwork.libcamera.org/api/patches/23609/comments/",
    "check": "pending",
    "checks": "https://patchwork.libcamera.org/api/patches/23609/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 529B7BDE6B\n\tfor <parsemail@patchwork.libcamera.org>;\n\tFri, 20 Jun 2025 12:45:06 +0000 (UTC)",
            "from lancelot.ideasonboard.com (localhost [IPv6:::1])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTP id CBD1C68DEE;\n\tFri, 20 Jun 2025 14:45:01 +0200 (CEST)",
            "from mail-wm1-x32d.google.com (mail-wm1-x32d.google.com\n\t[IPv6:2a00:1450:4864:20::32d])\n\tby lancelot.ideasonboard.com (Postfix) with ESMTPS id AB66861535\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tFri, 20 Jun 2025 14:44:58 +0200 (CEST)",
            "by mail-wm1-x32d.google.com with SMTP id\n\t5b1f17b1804b1-4535fc0485dso2484525e9.0\n\tfor <libcamera-devel@lists.libcamera.org>;\n\tFri, 20 Jun 2025 05:44:58 -0700 (PDT)",
            "from NAUSH-P-DELL.pitowers.org ([93.93.133.154])\n\tby smtp.gmail.com with ESMTPSA id\n\t5b1f17b1804b1-45361461375sm41561525e9.14.2025.06.20.05.44.57\n\t(version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256);\n\tFri, 20 Jun 2025 05:44:57 -0700 (PDT)"
        ],
        "Authentication-Results": "lancelot.ideasonboard.com; dkim=pass (2048-bit key;\n\tunprotected) header.d=raspberrypi.com header.i=@raspberrypi.com\n\theader.b=\"da19/KMT\"; dkim-atps=neutral",
        "DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=raspberrypi.com; s=google; t=1750423498; x=1751028298;\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=frwK0qays/9gk1irCD7nqtI6enlAuzK77v7RfYNMXpU=;\n\tb=da19/KMThAlxIvb8cLv1StG1YQfuGgSas6JHMQxO9LDen7V+6o5lzbrMfZ3UrmUvN0\n\tBtDRjjDWOSTFSItHpKCdgRsCBUKKdJ9ijjyebuQbogJy0AJcBhQnwtiw2NYJXIH4KUxq\n\tvaWlTebCHh/8PDmsqLs+QfX+fBJEIvwrXzRkOFmeogdS4Dk9kPu9L+iN4JaWzSO80kJi\n\tw/gMSjUf7jfK9mNMcFu/1zZ61iSUkHW+appJc+FpWy8hn/MoEXax+s8Lt3kjcB4DA/bc\n\tUdQRkpUYo/ijTmwL1eW45YXPZaD2VZIS6XhsauuHMmHS/UlhO2q93XnUJf8ZsTFzhwl+\n\tSfPQ==",
        "X-Google-DKIM-Signature": "v=1; a=rsa-sha256; c=relaxed/relaxed;\n\td=1e100.net; s=20230601; t=1750423498; x=1751028298;\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=frwK0qays/9gk1irCD7nqtI6enlAuzK77v7RfYNMXpU=;\n\tb=wRci9rvwBLad7WUBvHF6XS2ggsqcz399M3LxsqY5UUMVfUsr3ks2n0jAUvlXeqLE61\n\tbvAu/Kp4kFhcj/fXuEYSO4Ny+WTGrdmcwFOnpVlzWVGNhfYFBCnNeKlr05/e2irHOxy7\n\t9URGn+AeijVKCnblLLClp0UQIURkvJW2fWHFQdLqPv9vW1ULcwwS/ymHIBxC/en9Ak8B\n\tg1JB2/YAZZe1eA0FSiauCozGA9MUMgV/lERyT3n3tDdSMgABOm6Nb4EBGf4ApKW3rhOm\n\tbFEi8XuzJkWEgSvBYICYfDYFeJ7WT+4ajvTy7t8+pzMc61zIQzNJgo+tTvHn3lAMSJ/O\n\teRaQ==",
        "X-Gm-Message-State": "AOJu0YzSxiWUBvYeZQRFW5HNKffLmsMJBVXUR3zkyAzas/LeWgPswZ//\n\tCc4F1ulYd2oN3ZudvF7IFzUKdGndfcdAWMc1t5yZ+n4Su7BoDpB/EKI4nptrJ4GRo/ywXwRa06W\n\tQXt4B",
        "X-Gm-Gg": "ASbGncsxETV4ctw+ZC+hM66RIUux38PmOH8c1fJ5QvNiSRDSRGD7m3OeIIchuirdnfe\n\tr6fGwWfnc+zXz2Fe3OkUP1xX9CGSFuoXiuQnF0EqxY+xYb+Dl74SR+K6xG7sOtPV0iFrImRNY4B\n\t0yq65scKxvWs0boKJOJa0E7F2YRIbpcZ4e7lMV8bWxU6BFDceWaNx/OsYLMyTYZRA7wLjEWKG0e\n\teLhnDjsBev+Gh+onH4L1WKnmLlZs465gOGvsenyJ1Pe0R08Gzox0i1gOiAK0H0rv2JXHED18BYE\n\t3+NQ9D0b7H1jm2eAPSww1I0FrqJgRlVTDhQIRr/arR2dexleHrSoSxOubXU3VXevYk4rBUx1pQI\n\t+joLfhw==",
        "X-Google-Smtp-Source": "AGHT+IGhUDNGpV48uQ1ya2kZSs4uUM1kiclr7X4KmS9Qa0h9i+PybKGH6fZm9D0DZWjpkLZc8aGSZw==",
        "X-Received": "by 2002:a05:600c:1d0a:b0:451:dee4:cd07 with SMTP id\n\t5b1f17b1804b1-4536478e1e7mr10076895e9.0.1750423497870; \n\tFri, 20 Jun 2025 05:44:57 -0700 (PDT)",
        "From": "Naushir Patuck <naush@raspberrypi.com>",
        "To": "libcamera-devel@lists.libcamera.org",
        "Cc": "Nick Hollinghurst <nick.hollinghurst@raspberrypi.com>,\n\tNaushir Patuck <naush@raspberrypi.com>",
        "Subject": "[PATCH v1 1/8] ipa: rpi: Defer initialising AF LensPosition\n\tControlInfo and value",
        "Date": "Fri, 20 Jun 2025 13:42:22 +0100",
        "Message-ID": "<20250620124452.557855-2-naush@raspberrypi.com>",
        "X-Mailer": "git-send-email 2.43.0",
        "In-Reply-To": "<20250620124452.557855-1-naush@raspberrypi.com>",
        "References": "<20250620124452.557855-1-naush@raspberrypi.com>",
        "MIME-Version": "1.0",
        "Content-Transfer-Encoding": "8bit",
        "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": "From: Nick Hollinghurst <nick.hollinghurst@raspberrypi.com>\n\nThis fixes two small bugs:\n\nWe previously populated LensPosition's ControlInfo with hard-coded\nvalues, ignoring the tuning file. Now we query the AfAlgorithm to\nget limits (over all AF ranges) and default (for AfRangeNormal).\n\nWe previously sent a default position to the lens driver, even when\na user-specified starting position would follow. Defer doing this,\nto reduce unnecessary lens movement at startup (for some drivers).\n\nFixes: https://bugs.libcamera.org/show_bug.cgi?id=258\nSigned-off-by: Nick Hollinghurst <nick.hollinghurst@raspberrypi.com>\nSigned-off-by: Naushir Patuck <naush@raspberrypi.com>\nReviewed-by: Naushir Patuck <naush@raspberrypi.com>\n---\n src/ipa/rpi/common/ipa_base.cpp       | 53 +++++++++++++++++----------\n src/ipa/rpi/controller/af_algorithm.h |  8 +++-\n src/ipa/rpi/controller/rpi/af.cpp     | 16 +++++++-\n src/ipa/rpi/controller/rpi/af.h       |  4 +-\n 4 files changed, 57 insertions(+), 24 deletions(-)",
    "diff": "diff --git a/src/ipa/rpi/common/ipa_base.cpp b/src/ipa/rpi/common/ipa_base.cpp\nindex 6565f5366312..a5bdcbb5838c 100644\n--- a/src/ipa/rpi/common/ipa_base.cpp\n+++ b/src/ipa/rpi/common/ipa_base.cpp\n@@ -233,25 +233,6 @@ int32_t IpaBase::configure(const IPACameraSensorInfo &sensorInfo, const ConfigPa\n \t\tagcStatus.analogueGain = defaultAnalogueGain;\n \t\tapplyAGC(&agcStatus, ctrls);\n \n-\t\t/*\n-\t\t * Set the lens to the default (typically hyperfocal) position\n-\t\t * on first start.\n-\t\t */\n-\t\tif (lensPresent_) {\n-\t\t\tRPiController::AfAlgorithm *af =\n-\t\t\t\tdynamic_cast<RPiController::AfAlgorithm *>(controller_.getAlgorithm(\"af\"));\n-\n-\t\t\tif (af) {\n-\t\t\t\tfloat defaultPos =\n-\t\t\t\t\tipaAfControls.at(&controls::LensPosition).def().get<float>();\n-\t\t\t\tControlList lensCtrl(lensCtrls_);\n-\t\t\t\tint32_t hwpos;\n-\n-\t\t\t\taf->setLensPosition(defaultPos, &hwpos);\n-\t\t\t\tlensCtrl.set(V4L2_CID_FOCUS_ABSOLUTE, hwpos);\n-\t\t\t\tresult->lensControls = std::move(lensCtrl);\n-\t\t\t}\n-\t\t}\n \t}\n \n \tresult->sensorControls = std::move(ctrls);\n@@ -281,8 +262,20 @@ int32_t IpaBase::configure(const IPACameraSensorInfo &sensorInfo, const ConfigPa\n \t\tctrlMap.merge(ControlInfoMap::Map(ipaColourControls));\n \n \t/* Declare Autofocus controls, only if we have a controllable lens */\n-\tif (lensPresent_)\n+\tif (lensPresent_) {\n \t\tctrlMap.merge(ControlInfoMap::Map(ipaAfControls));\n+\t\tRPiController::AfAlgorithm *af =\n+\t\t\tdynamic_cast<RPiController::AfAlgorithm *>(controller_.getAlgorithm(\"af\"));\n+\t\tif (af) {\n+\t\t\tdouble min, max, dflt;\n+\t\t\taf->getLensLimits(min, max);\n+\t\t\tdflt = af->getDefaultLensPosition();\n+\t\t\tctrlMap[&controls::LensPosition] =\n+\t\t\t\tControlInfo(static_cast<float>(min),\n+\t\t\t\t\t    static_cast<float>(max),\n+\t\t\t\t\t    static_cast<float>(dflt));\n+\t\t}\n+\t}\n \n \tresult->controlInfo = ControlInfoMap(std::move(ctrlMap), controls::controls);\n \n@@ -320,6 +313,26 @@ void IpaBase::start(const ControlList &controls, StartResult *result)\n \t/* Make a note of this as it tells us the HDR status of the first few frames. */\n \thdrStatus_ = agcStatus.hdr;\n \n+\t/*\n+\t * AF: If no lens position was specified, drive lens to a default position.\n+\t * This had to be deferred (not initialised by a constructor) until here\n+\t * to ensure that exactly ONE starting position is sent to the lens driver.\n+\t * It should be the static API default, not dependent on AF range or mode.\n+\t */\n+\tif (firstStart_ && lensPresent_) {\n+\t\tRPiController::AfAlgorithm *af = dynamic_cast<RPiController::AfAlgorithm *>(\n+\t\t\tcontroller_.getAlgorithm(\"af\"));\n+\t\tif (af && !af->getLensPosition()) {\n+\t\t\tint32_t hwpos;\n+\t\t\tdouble pos = af->getDefaultLensPosition();\n+\t\t\tif (af->setLensPosition(pos, &hwpos, true)) {\n+\t\t\t\tControlList lensCtrls(lensCtrls_);\n+\t\t\t\tlensCtrls.set(V4L2_CID_FOCUS_ABSOLUTE, hwpos);\n+\t\t\t\tsetLensControls.emit(lensCtrls);\n+\t\t\t}\n+\t\t}\n+\t}\n+\n \t/*\n \t * Initialise frame counts, and decide how many frames must be hidden or\n \t * \"mistrusted\", which depends on whether this is a startup from cold,\ndiff --git a/src/ipa/rpi/controller/af_algorithm.h b/src/ipa/rpi/controller/af_algorithm.h\nindex ad9b575450e3..382609f9b2d8 100644\n--- a/src/ipa/rpi/controller/af_algorithm.h\n+++ b/src/ipa/rpi/controller/af_algorithm.h\n@@ -33,6 +33,10 @@ public:\n \t *\n \t * getMode() is provided mainly for validating controls.\n \t * getLensPosition() is provided for populating DeviceStatus.\n+\t *\n+\t * getDefaultlensPosition() and getLensLimits() were added for\n+\t * populating ControlInfoMap. They return the static API limits\n+\t * which should be independent of the current range or mode.\n \t */\n \n \tenum AfRange { AfRangeNormal = 0,\n@@ -66,7 +70,9 @@ public:\n \t}\n \tvirtual void setMode(AfMode mode) = 0;\n \tvirtual AfMode getMode() const = 0;\n-\tvirtual bool setLensPosition(double dioptres, int32_t *hwpos) = 0;\n+\tvirtual double getDefaultLensPosition() const = 0;\n+\tvirtual void getLensLimits(double &min, double &max) const = 0;\n+\tvirtual bool setLensPosition(double dioptres, int32_t *hwpos, bool force = false) = 0;\n \tvirtual std::optional<double> getLensPosition() const = 0;\n \tvirtual void triggerScan() = 0;\n \tvirtual void cancelScan() = 0;\ndiff --git a/src/ipa/rpi/controller/rpi/af.cpp b/src/ipa/rpi/controller/rpi/af.cpp\nindex 2157eb94f427..041cb51db277 100644\n--- a/src/ipa/rpi/controller/rpi/af.cpp\n+++ b/src/ipa/rpi/controller/rpi/af.cpp\n@@ -715,11 +715,23 @@ void Af::setWindows(libcamera::Span<libcamera::Rectangle const> const &wins)\n \t\tinvalidateWeights();\n }\n \n-bool Af::setLensPosition(double dioptres, int *hwpos)\n+double Af::getDefaultLensPosition() const\n+{\n+\treturn cfg_.ranges[AfRangeNormal].focusDefault;\n+}\n+\n+void Af::getLensLimits(double &min, double &max) const\n+{\n+\t/* Limits for manual focus are set by map, not by ranges */\n+\tmin = cfg_.map.domain().start;\n+\tmax = cfg_.map.domain().end;\n+}\n+\n+bool Af::setLensPosition(double dioptres, int *hwpos, bool force)\n {\n \tbool changed = false;\n \n-\tif (mode_ == AfModeManual) {\n+\tif (mode_ == AfModeManual || force) {\n \t\tLOG(RPiAf, Debug) << \"setLensPosition: \" << dioptres;\n \t\tftarget_ = cfg_.map.domain().clamp(dioptres);\n \t\tchanged = !(initted_ && fsmooth_ == ftarget_);\ndiff --git a/src/ipa/rpi/controller/rpi/af.h b/src/ipa/rpi/controller/rpi/af.h\nindex 317a51f3ecc3..d8ece1ab119c 100644\n--- a/src/ipa/rpi/controller/rpi/af.h\n+++ b/src/ipa/rpi/controller/rpi/af.h\n@@ -54,7 +54,9 @@ public:\n \tvoid setWindows(libcamera::Span<libcamera::Rectangle const> const &wins) override;\n \tvoid setMode(AfMode mode) override;\n \tAfMode getMode() const override;\n-\tbool setLensPosition(double dioptres, int32_t *hwpos) override;\n+\tdouble getDefaultLensPosition() const override;\n+\tvoid getLensLimits(double &min, double &max) const override;\n+\tbool setLensPosition(double dioptres, int32_t *hwpos, bool force) override;\n \tstd::optional<double> getLensPosition() const override;\n \tvoid triggerScan() override;\n \tvoid cancelScan() override;\n",
    "prefixes": [
        "v1",
        "1/8"
    ]
}