Skip to content

Instantly share code, notes, and snippets.

@flokli
Created January 28, 2024 14:39
Show Gist options
  • Save flokli/4f510cef361c41694b2aa35cc63ac0cd to your computer and use it in GitHub Desktop.
Save flokli/4f510cef361c41694b2aa35cc63ac0cd to your computer and use it in GitHub Desktop.
attempt to get ipu6-softisp to work
From a2f6a1bb1536801e7f4cbd0af679dd01b6fc020a Mon Sep 17 00:00:00 2001
From: Florian Klink <flokli@flokli.de>
Date: Sat, 27 Jan 2024 15:51:40 +0200
Subject: [PATCH 1/3] use libcamera software ISP
---
machines/x1/default.nix | 15 +-
nix/overlay.nix | 54 ++
...ne-simple-fix-size-adjustment-in-val.patch | 43 ++
...al-Move-dma_heaps.-h-cpp-to-common-d.patch | 194 +++++
...aps-extend-DmaHeap-class-to-support-.patch | 121 +++
...al-Move-SharedMemObject-class-to-a-c.patch | 57 ++
...al-Document-the-SharedMemObject-clas.patch | 139 ++++
...ibcamera-introduce-SoftwareIsp-class.patch | 354 +++++++++
...-software_isp-Add-SwStats-base-class.patch | 382 +++++++++
...ra-software_isp-Add-SwStatsCpu-class.patch | 272 +++++++
...-software_isp-Add-Debayer-base-class.patch | 272 +++++++
...ra-software_isp-Add-DebayerCpu-class.patch | 727 ++++++++++++++++++
...camera-ipa-add-Soft-IPA-common-files.patch | 256 ++++++
...ft-IPA-add-a-Simple-Soft-IPA-impleme.patch | 407 ++++++++++
...re_isp-add-Simple-SoftwareIsp-implem.patch | 483 ++++++++++++
...ne-simple-rename-converterBuffers_-a.patch | 238 ++++++
...ne-simple-enable-use-of-Soft-ISP-and.patch | 243 ++++++
...s_cpu-Add-support-for-8-10-and-12-bp.patch | 199 +++++
...r_cpu-Add-support-for-8-10-and-12-bp.patch | 237 ++++++
...ebayer_cpu-Add-BGR888-output-support.patch | 125 +++
...ne-simple-Enable-simplepipeline-for-.patch | 30 +
...pport-for-IGIG_GBGR_IGIG_GRGB-bayer-.patch | 237 ++++++
...s_cpu-Add-support-for-10bpp-IGIG_GBG.patch | 131 ++++
...r_cpu-Add-support-for-10bpp-IGIG_GBG.patch | 315 ++++++++
...ftware-ISP-benchmarking-documentatio.patch | 130 ++++
nix/pkgs/libcamera/0024-ov01a1s-HACK.patch | 95 +++
...r_cpu-Make-the-minimum-size-1280x720.patch | 40 +
27 files changed, 5789 insertions(+), 7 deletions(-)
create mode 100644 nix/pkgs/libcamera/0001-libcamera-pipeline-simple-fix-size-adjustment-in-val.patch
create mode 100644 nix/pkgs/libcamera/0002-libcamera-internal-Move-dma_heaps.-h-cpp-to-common-d.patch
create mode 100644 nix/pkgs/libcamera/0003-libcamera-dma_heaps-extend-DmaHeap-class-to-support-.patch
create mode 100644 nix/pkgs/libcamera/0004-libcamera-internal-Move-SharedMemObject-class-to-a-c.patch
create mode 100644 nix/pkgs/libcamera/0005-libcamera-internal-Document-the-SharedMemObject-clas.patch
create mode 100644 nix/pkgs/libcamera/0006-libcamera-introduce-SoftwareIsp-class.patch
create mode 100644 nix/pkgs/libcamera/0007-libcamera-software_isp-Add-SwStats-base-class.patch
create mode 100644 nix/pkgs/libcamera/0008-libcamera-software_isp-Add-SwStatsCpu-class.patch
create mode 100644 nix/pkgs/libcamera/0009-libcamera-software_isp-Add-Debayer-base-class.patch
create mode 100644 nix/pkgs/libcamera/0010-libcamera-software_isp-Add-DebayerCpu-class.patch
create mode 100644 nix/pkgs/libcamera/0011-libcamera-ipa-add-Soft-IPA-common-files.patch
create mode 100644 nix/pkgs/libcamera/0012-libcamera-ipa-Soft-IPA-add-a-Simple-Soft-IPA-impleme.patch
create mode 100644 nix/pkgs/libcamera/0013-libcamera-software_isp-add-Simple-SoftwareIsp-implem.patch
create mode 100644 nix/pkgs/libcamera/0014-libcamera-pipeline-simple-rename-converterBuffers_-a.patch
create mode 100644 nix/pkgs/libcamera/0015-libcamera-pipeline-simple-enable-use-of-Soft-ISP-and.patch
create mode 100644 nix/pkgs/libcamera/0016-libcamera-swstats_cpu-Add-support-for-8-10-and-12-bp.patch
create mode 100644 nix/pkgs/libcamera/0017-libcamera-debayer_cpu-Add-support-for-8-10-and-12-bp.patch
create mode 100644 nix/pkgs/libcamera/0018-libcamera-debayer_cpu-Add-BGR888-output-support.patch
create mode 100644 nix/pkgs/libcamera/0019-libcamera-pipeline-simple-Enable-simplepipeline-for-.patch
create mode 100644 nix/pkgs/libcamera/0020-libcamera-Add-support-for-IGIG_GBGR_IGIG_GRGB-bayer-.patch
create mode 100644 nix/pkgs/libcamera/0021-libcamera-swstats_cpu-Add-support-for-10bpp-IGIG_GBG.patch
create mode 100644 nix/pkgs/libcamera/0022-libcamera-debayer_cpu-Add-support-for-10bpp-IGIG_GBG.patch
create mode 100644 nix/pkgs/libcamera/0023-libcamera-Add-Software-ISP-benchmarking-documentatio.patch
create mode 100644 nix/pkgs/libcamera/0024-ov01a1s-HACK.patch
create mode 100644 nix/pkgs/libcamera/0025-libcamera-debayer_cpu-Make-the-minimum-size-1280x720.patch
diff --git a/machines/x1/default.nix b/machines/x1/default.nix
index ab4d250..d2e96a3 100644
--- a/machines/x1/default.nix
+++ b/machines/x1/default.nix
@@ -65,12 +65,6 @@ in
# };
boot.blacklistedKernelModules = [ "iosm" ];
-
- hardware.ipu6 = {
- enable = true;
- platform = "ipu6ep";
- };
-
boot.binfmt.emulatedSystems = [ "aarch64-linux" ];
boot = {
@@ -89,7 +83,14 @@ in
kernel.sysctl."vm.swappiness" = 100;
kernelModules = [ "kvm-intel" ];
- kernelPackages = pkgs.linuxPackages_6_6;
+ kernelPackages = pkgs.linuxPackages_latest;
+ kernelPatches = [{
+ name = "linux-kernel-test.patch";
+ patch = pkgs.fetchurl {
+ url = "https://copr-dist-git.fedorainfracloud.org/cgit/jwrdegoede/ipu6-softisp/kernel.git/plain/linux-kernel-test.patch?h=f39&id=0ed76891b2fc08579d08bedb9294a41840007299";
+ hash = "sha256-8hKP4nltGlkzr8iOgsIUT9Tt5i+x4kdLmw/+lFeNoGQ=";
+ };
+ }];
};
fileSystems."/" = lib.mkOverride 999 {
diff --git a/nix/overlay.nix b/nix/overlay.nix
index ce6039b..da294e5 100644
--- a/nix/overlay.nix
+++ b/nix/overlay.nix
@@ -60,6 +60,60 @@ self: super: rec {
vendorSha256 = "sha256-xZkxLVIxG4I4CgrJ9VXDV/UUWxVE+xcipJECCNRgmP8=";
};
+ libcamera = super.libcamera.overrideAttrs (old: {
+ # This is a mix of #281755 (bump pipewire to 0.2.0),
+ # and the additional ipu6-softisp patches.
+ version = "0.2.0";
+ src = super.fetchgit {
+ url = "https://git.libcamera.org/libcamera/libcamera.git";
+ rev = "v0.2.0";
+ hash = "sha256-x0Im9m9MoACJhQKorMI34YQ+/bd62NdAPc2nWwaJAvM=";
+ };
+
+ # Explicitly clear list of patches, as #281755 did.
+ # This is
+ # https://copr-dist-git.fedorainfracloud.org/cgit/jwrdegoede/ipu6-softisp/libcamera.git/plain/libcamera-0.2.0-softisp.patch?h=f39&id=60e6b3d5e366a360a75942073dc0d642e4900982,
+ # but manually piped to git and back, as some renames were not processed properly.
+ patches = [
+ ./pkgs/libcamera/0001-libcamera-pipeline-simple-fix-size-adjustment-in-val.patch
+ ./pkgs/libcamera/0002-libcamera-internal-Move-dma_heaps.-h-cpp-to-common-d.patch
+ ./pkgs/libcamera/0003-libcamera-dma_heaps-extend-DmaHeap-class-to-support-.patch
+ ./pkgs/libcamera/0004-libcamera-internal-Move-SharedMemObject-class-to-a-c.patch
+ ./pkgs/libcamera/0005-libcamera-internal-Document-the-SharedMemObject-clas.patch
+ ./pkgs/libcamera/0006-libcamera-introduce-SoftwareIsp-class.patch
+ ./pkgs/libcamera/0007-libcamera-software_isp-Add-SwStats-base-class.patch
+ ./pkgs/libcamera/0008-libcamera-software_isp-Add-SwStatsCpu-class.patch
+ ./pkgs/libcamera/0009-libcamera-software_isp-Add-Debayer-base-class.patch
+ ./pkgs/libcamera/0010-libcamera-software_isp-Add-DebayerCpu-class.patch
+ ./pkgs/libcamera/0011-libcamera-ipa-add-Soft-IPA-common-files.patch
+ ./pkgs/libcamera/0012-libcamera-ipa-Soft-IPA-add-a-Simple-Soft-IPA-impleme.patch
+ ./pkgs/libcamera/0013-libcamera-software_isp-add-Simple-SoftwareIsp-implem.patch
+ ./pkgs/libcamera/0014-libcamera-pipeline-simple-rename-converterBuffers_-a.patch
+ ./pkgs/libcamera/0015-libcamera-pipeline-simple-enable-use-of-Soft-ISP-and.patch
+ ./pkgs/libcamera/0016-libcamera-swstats_cpu-Add-support-for-8-10-and-12-bp.patch
+ ./pkgs/libcamera/0017-libcamera-debayer_cpu-Add-support-for-8-10-and-12-bp.patch
+ ./pkgs/libcamera/0018-libcamera-debayer_cpu-Add-BGR888-output-support.patch
+ ./pkgs/libcamera/0019-libcamera-pipeline-simple-Enable-simplepipeline-for-.patch
+ ./pkgs/libcamera/0020-libcamera-Add-support-for-IGIG_GBGR_IGIG_GRGB-bayer-.patch
+ ./pkgs/libcamera/0021-libcamera-swstats_cpu-Add-support-for-10bpp-IGIG_GBG.patch
+ ./pkgs/libcamera/0022-libcamera-debayer_cpu-Add-support-for-10bpp-IGIG_GBG.patch
+ ./pkgs/libcamera/0023-libcamera-Add-Software-ISP-benchmarking-documentatio.patch
+ ./pkgs/libcamera/0024-ov01a1s-HACK.patch
+ ./pkgs/libcamera/0025-libcamera-debayer_cpu-Make-the-minimum-size-1280x720.patch
+ ];
+ });
+
+ pipewire = super.pipewire.overrideAttrs (old: {
+ patches = old.patches or [ ] ++ [
+ (super.fetchpatch {
+ # https://gitlab.freedesktop.org/pipewire/pipewire/-/merge_requests/1750
+ name = "pipewire-spa-libcamera-use-cameraconfiguration-orientation-pr1750.patch";
+ url = "https://gitlab.freedesktop.org/pipewire/pipewire/-/merge_requests/1750.patch ";
+ hash = "sha256-Ugg913KZDKELnYLwpDEgYh92YPxccw61l6kAJulBbIA=";
+ })
+ ];
+ });
+
myWeechat =
(super.weechat.override {
configure = { availablePlugins, ... }: {
diff --git a/nix/pkgs/libcamera/0001-libcamera-pipeline-simple-fix-size-adjustment-in-val.patch b/nix/pkgs/libcamera/0001-libcamera-pipeline-simple-fix-size-adjustment-in-val.patch
new file mode 100644
index 0000000..7a71ed1
--- /dev/null
+++ b/nix/pkgs/libcamera/0001-libcamera-pipeline-simple-fix-size-adjustment-in-val.patch
@@ -0,0 +1,43 @@
+From aa818f7b749122f916be1ced48d1a3a2b3aeb47e Mon Sep 17 00:00:00 2001
+From: Andrey Konovalov <andrey.konovalov@linaro.org>
+Date: Tue, 2 Jan 2024 23:47:20 +0300
+Subject: [PATCH 01/25] libcamera: pipeline: simple: fix size adjustment in
+ validate()
+
+SimpleCameraConfiguration::validate() adjusts the configuration
+of its streams (if the size is not in the outputSizes) to
+the captureSize. But the captureSize itself can be not in the
+outputSizes, and then the adjusted configuration won't be
+valid resulting in camera configuration failure.
+
+Signed-off-by: Andrey Konovalov <andrey.konovalov@linaro.org>
+Signed-off-by: Hans de Goede <hdegoede@redhat.com>
+Tested-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org> # sc8280xp Lenovo x13s
+Tested-by: Pavel Machek <pavel@ucw.cz>
+---
+ src/libcamera/pipeline/simple/simple.cpp | 7 +++++--
+ 1 file changed, 5 insertions(+), 2 deletions(-)
+
+diff --git a/src/libcamera/pipeline/simple/simple.cpp b/src/libcamera/pipeline/simple/simple.cpp
+index 911051b2..4d0e7255 100644
+--- a/src/libcamera/pipeline/simple/simple.cpp
++++ b/src/libcamera/pipeline/simple/simple.cpp
+@@ -997,10 +997,13 @@ CameraConfiguration::Status SimpleCameraConfiguration::validate()
+ }
+
+ if (!pipeConfig_->outputSizes.contains(cfg.size)) {
++ Size adjustedSize = pipeConfig_->captureSize;
++ if (!pipeConfig_->outputSizes.contains(adjustedSize))
++ adjustedSize = pipeConfig_->outputSizes.max;
+ LOG(SimplePipeline, Debug)
+ << "Adjusting size from " << cfg.size
+- << " to " << pipeConfig_->captureSize;
+- cfg.size = pipeConfig_->captureSize;
++ << " to " << adjustedSize;
++ cfg.size = adjustedSize;
+ status = Adjusted;
+ }
+
+--
+2.43.0
+
diff --git a/nix/pkgs/libcamera/0002-libcamera-internal-Move-dma_heaps.-h-cpp-to-common-d.patch b/nix/pkgs/libcamera/0002-libcamera-internal-Move-dma_heaps.-h-cpp-to-common-d.patch
new file mode 100644
index 0000000..85a27ba
--- /dev/null
+++ b/nix/pkgs/libcamera/0002-libcamera-internal-Move-dma_heaps.-h-cpp-to-common-d.patch
@@ -0,0 +1,194 @@
+From ca3bcfde49f069a85f7860f61d8c3bd196f97139 Mon Sep 17 00:00:00 2001
+From: Andrey Konovalov <andrey.konovalov@linaro.org>
+Date: Tue, 26 Dec 2023 16:55:08 +0300
+Subject: [PATCH 02/25] libcamera: internal: Move dma_heaps.[h,cpp] to common
+ directories
+
+DmaHeap class is useful outside the RPi pipeline handler too.
+
+Move dma_heaps.h and dma_heaps.cpp to common directories. Update
+the build files and RPi vc4 pipeline handler accordingly.
+
+Signed-off-by: Andrey Konovalov <andrey.konovalov@linaro.org>
+Signed-off-by: Hans de Goede <hdegoede@redhat.com>
+Tested-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org> # sc8280xp Lenovo x13s
+Tested-by: Pavel Machek <pavel@ucw.cz>
+---
+ .../libcamera/internal}/dma_heaps.h | 4 ----
+ include/libcamera/internal/meson.build | 1 +
+ .../{pipeline/rpi/vc4 => }/dma_heaps.cpp | 18 +++++++-----------
+ src/libcamera/meson.build | 1 +
+ src/libcamera/pipeline/rpi/vc4/meson.build | 1 -
+ src/libcamera/pipeline/rpi/vc4/vc4.cpp | 5 ++---
+ 6 files changed, 11 insertions(+), 19 deletions(-)
+ rename {src/libcamera/pipeline/rpi/vc4 => include/libcamera/internal}/dma_heaps.h (92%)
+ rename src/libcamera/{pipeline/rpi/vc4 => }/dma_heaps.cpp (83%)
+
+diff --git a/src/libcamera/pipeline/rpi/vc4/dma_heaps.h b/include/libcamera/internal/dma_heaps.h
+similarity index 92%
+rename from src/libcamera/pipeline/rpi/vc4/dma_heaps.h
+rename to include/libcamera/internal/dma_heaps.h
+index 0a4a8d86..cff8f140 100644
+--- a/src/libcamera/pipeline/rpi/vc4/dma_heaps.h
++++ b/include/libcamera/internal/dma_heaps.h
+@@ -13,8 +13,6 @@
+
+ namespace libcamera {
+
+-namespace RPi {
+-
+ class DmaHeap
+ {
+ public:
+@@ -27,6 +25,4 @@ private:
+ UniqueFD dmaHeapHandle_;
+ };
+
+-} /* namespace RPi */
+-
+ } /* namespace libcamera */
+diff --git a/include/libcamera/internal/meson.build b/include/libcamera/internal/meson.build
+index 7f1f3440..33eb0fb3 100644
+--- a/include/libcamera/internal/meson.build
++++ b/include/libcamera/internal/meson.build
+@@ -25,6 +25,7 @@ libcamera_internal_headers = files([
+ 'device_enumerator.h',
+ 'device_enumerator_sysfs.h',
+ 'device_enumerator_udev.h',
++ 'dma_heaps.h',
+ 'formats.h',
+ 'framebuffer.h',
+ 'ipa_manager.h',
+diff --git a/src/libcamera/pipeline/rpi/vc4/dma_heaps.cpp b/src/libcamera/dma_heaps.cpp
+similarity index 83%
+rename from src/libcamera/pipeline/rpi/vc4/dma_heaps.cpp
+rename to src/libcamera/dma_heaps.cpp
+index 317b1fc1..7444d9c2 100644
+--- a/src/libcamera/pipeline/rpi/vc4/dma_heaps.cpp
++++ b/src/libcamera/dma_heaps.cpp
+@@ -5,8 +5,6 @@
+ * dma_heaps.h - Helper class for dma-heap allocations.
+ */
+
+-#include "dma_heaps.h"
+-
+ #include <array>
+ #include <fcntl.h>
+ #include <linux/dma-buf.h>
+@@ -16,6 +14,8 @@
+
+ #include <libcamera/base/log.h>
+
++#include "libcamera/internal/dma_heaps.h"
++
+ /*
+ * /dev/dma-heap/linux,cma is the dma-heap allocator, which allows dmaheap-cma
+ * to only have to worry about importing.
+@@ -30,9 +30,7 @@ static constexpr std::array<const char *, 2> heapNames = {
+
+ namespace libcamera {
+
+-LOG_DECLARE_CATEGORY(RPI)
+-
+-namespace RPi {
++LOG_DEFINE_CATEGORY(DmaHeap)
+
+ DmaHeap::DmaHeap()
+ {
+@@ -40,7 +38,7 @@ DmaHeap::DmaHeap()
+ int ret = ::open(name, O_RDWR | O_CLOEXEC, 0);
+ if (ret < 0) {
+ ret = errno;
+- LOG(RPI, Debug) << "Failed to open " << name << ": "
++ LOG(DmaHeap, Debug) << "Failed to open " << name << ": "
+ << strerror(ret);
+ continue;
+ }
+@@ -50,7 +48,7 @@ DmaHeap::DmaHeap()
+ }
+
+ if (!dmaHeapHandle_.isValid())
+- LOG(RPI, Error) << "Could not open any dmaHeap device";
++ LOG(DmaHeap, Error) << "Could not open any dmaHeap device";
+ }
+
+ DmaHeap::~DmaHeap() = default;
+@@ -69,7 +67,7 @@ UniqueFD DmaHeap::alloc(const char *name, std::size_t size)
+
+ ret = ::ioctl(dmaHeapHandle_.get(), DMA_HEAP_IOCTL_ALLOC, &alloc);
+ if (ret < 0) {
+- LOG(RPI, Error) << "dmaHeap allocation failure for "
++ LOG(DmaHeap, Error) << "dmaHeap allocation failure for "
+ << name;
+ return {};
+ }
+@@ -77,7 +75,7 @@ UniqueFD DmaHeap::alloc(const char *name, std::size_t size)
+ UniqueFD allocFd(alloc.fd);
+ ret = ::ioctl(allocFd.get(), DMA_BUF_SET_NAME, name);
+ if (ret < 0) {
+- LOG(RPI, Error) << "dmaHeap naming failure for "
++ LOG(DmaHeap, Error) << "dmaHeap naming failure for "
+ << name;
+ return {};
+ }
+@@ -85,6 +83,4 @@ UniqueFD DmaHeap::alloc(const char *name, std::size_t size)
+ return allocFd;
+ }
+
+-} /* namespace RPi */
+-
+ } /* namespace libcamera */
+diff --git a/src/libcamera/meson.build b/src/libcamera/meson.build
+index 45f63e93..3c5e43df 100644
+--- a/src/libcamera/meson.build
++++ b/src/libcamera/meson.build
+@@ -17,6 +17,7 @@ libcamera_sources = files([
+ 'delayed_controls.cpp',
+ 'device_enumerator.cpp',
+ 'device_enumerator_sysfs.cpp',
++ 'dma_heaps.cpp',
+ 'fence.cpp',
+ 'formats.cpp',
+ 'framebuffer.cpp',
+diff --git a/src/libcamera/pipeline/rpi/vc4/meson.build b/src/libcamera/pipeline/rpi/vc4/meson.build
+index cdb049c5..386e2296 100644
+--- a/src/libcamera/pipeline/rpi/vc4/meson.build
++++ b/src/libcamera/pipeline/rpi/vc4/meson.build
+@@ -1,7 +1,6 @@
+ # SPDX-License-Identifier: CC0-1.0
+
+ libcamera_sources += files([
+- 'dma_heaps.cpp',
+ 'vc4.cpp',
+ ])
+
+diff --git a/src/libcamera/pipeline/rpi/vc4/vc4.cpp b/src/libcamera/pipeline/rpi/vc4/vc4.cpp
+index 26102ea7..3a42e75e 100644
+--- a/src/libcamera/pipeline/rpi/vc4/vc4.cpp
++++ b/src/libcamera/pipeline/rpi/vc4/vc4.cpp
+@@ -12,12 +12,11 @@
+ #include <libcamera/formats.h>
+
+ #include "libcamera/internal/device_enumerator.h"
++#include "libcamera/internal/dma_heaps.h"
+
+ #include "../common/pipeline_base.h"
+ #include "../common/rpi_stream.h"
+
+-#include "dma_heaps.h"
+-
+ using namespace std::chrono_literals;
+
+ namespace libcamera {
+@@ -87,7 +86,7 @@ public:
+ RPi::Device<Isp, 4> isp_;
+
+ /* DMAHEAP allocation helper. */
+- RPi::DmaHeap dmaHeap_;
++ DmaHeap dmaHeap_;
+ SharedFD lsTable_;
+
+ struct Config {
+--
+2.43.0
+
diff --git a/nix/pkgs/libcamera/0003-libcamera-dma_heaps-extend-DmaHeap-class-to-support-.patch b/nix/pkgs/libcamera/0003-libcamera-dma_heaps-extend-DmaHeap-class-to-support-.patch
new file mode 100644
index 0000000..de81924
--- /dev/null
+++ b/nix/pkgs/libcamera/0003-libcamera-dma_heaps-extend-DmaHeap-class-to-support-.patch
@@ -0,0 +1,121 @@
+From 6d5f3b0b54df4ff66079675a4c1f0f0b76778e22 Mon Sep 17 00:00:00 2001
+From: Andrey Konovalov <andrey.konovalov@linaro.org>
+Date: Wed, 10 Jan 2024 23:51:25 +0300
+Subject: [PATCH 03/25] libcamera: dma_heaps: extend DmaHeap class to support
+ system heap
+
+Add an argument to the constructor to specify dma heaps type(s)
+to use. Can be DmaHeapFlag::Cma and/or DmaHeapFlag::System.
+By default DmaHeapFlag::Cma is used. If both DmaHeapFlag::Cma and
+DmaHeapFlag::System are set, CMA heap is tried first.
+
+Signed-off-by: Andrey Konovalov <andrey.konovalov@linaro.org>
+Signed-off-by: Hans de Goede <hdegoede@redhat.com>
+Tested-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org> # sc8280xp Lenovo x13s
+Tested-by: Pavel Machek <pavel@ucw.cz>
+---
+ include/libcamera/internal/dma_heaps.h | 12 +++++++-
+ src/libcamera/dma_heaps.cpp | 39 +++++++++++++++-----------
+ 2 files changed, 34 insertions(+), 17 deletions(-)
+
+diff --git a/include/libcamera/internal/dma_heaps.h b/include/libcamera/internal/dma_heaps.h
+index cff8f140..22aa1007 100644
+--- a/include/libcamera/internal/dma_heaps.h
++++ b/include/libcamera/internal/dma_heaps.h
+@@ -9,6 +9,7 @@
+
+ #include <stddef.h>
+
++#include <libcamera/base/flags.h>
+ #include <libcamera/base/unique_fd.h>
+
+ namespace libcamera {
+@@ -16,7 +17,14 @@ namespace libcamera {
+ class DmaHeap
+ {
+ public:
+- DmaHeap();
++ enum class DmaHeapFlag {
++ Cma = (1 << 0),
++ System = (1 << 1),
++ };
++
++ using DmaHeapFlags = Flags<DmaHeapFlag>;
++
++ DmaHeap(DmaHeapFlags flags = DmaHeapFlag::Cma);
+ ~DmaHeap();
+ bool isValid() const { return dmaHeapHandle_.isValid(); }
+ UniqueFD alloc(const char *name, std::size_t size);
+@@ -25,4 +33,6 @@ private:
+ UniqueFD dmaHeapHandle_;
+ };
+
++LIBCAMERA_FLAGS_ENABLE_OPERATORS(DmaHeap::DmaHeapFlag)
++
+ } /* namespace libcamera */
+diff --git a/src/libcamera/dma_heaps.cpp b/src/libcamera/dma_heaps.cpp
+index 7444d9c2..177de31b 100644
+--- a/src/libcamera/dma_heaps.cpp
++++ b/src/libcamera/dma_heaps.cpp
+@@ -16,6 +16,8 @@
+
+ #include "libcamera/internal/dma_heaps.h"
+
++namespace libcamera {
++
+ /*
+ * /dev/dma-heap/linux,cma is the dma-heap allocator, which allows dmaheap-cma
+ * to only have to worry about importing.
+@@ -23,28 +25,33 @@
+ * Annoyingly, should the cma heap size be specified on the kernel command line
+ * instead of DT, the heap gets named "reserved" instead.
+ */
+-static constexpr std::array<const char *, 2> heapNames = {
+- "/dev/dma_heap/linux,cma",
+- "/dev/dma_heap/reserved"
++static constexpr std::array<std::pair<DmaHeap::DmaHeapFlag, const char *>, 3> heapNames = {
++ /* CMA heap names first */
++ std::make_pair(DmaHeap::DmaHeapFlag::Cma, "/dev/dma_heap/linux,cma"),
++ std::make_pair(DmaHeap::DmaHeapFlag::Cma, "/dev/dma_heap/reserved"),
++ std::make_pair(DmaHeap::DmaHeapFlag::System, "/dev/dma_heap/system")
+ };
+
+-namespace libcamera {
+-
+ LOG_DEFINE_CATEGORY(DmaHeap)
+
+-DmaHeap::DmaHeap()
++DmaHeap::DmaHeap(DmaHeapFlags flags)
+ {
+- for (const char *name : heapNames) {
+- int ret = ::open(name, O_RDWR | O_CLOEXEC, 0);
+- if (ret < 0) {
+- ret = errno;
+- LOG(DmaHeap, Debug) << "Failed to open " << name << ": "
+- << strerror(ret);
+- continue;
+- }
++ int ret;
+
+- dmaHeapHandle_ = UniqueFD(ret);
+- break;
++ for (const auto &name : heapNames) {
++ if (flags & name.first) {
++ ret = ::open(name.second, O_RDWR | O_CLOEXEC, 0);
++ if (ret < 0) {
++ ret = errno;
++ LOG(DmaHeap, Debug) << "Failed to open " << name.second << ": "
++ << strerror(ret);
++ continue;
++ }
++
++ LOG(DmaHeap, Debug) << "Using " << name.second;
++ dmaHeapHandle_ = UniqueFD(ret);
++ break;
++ }
+ }
+
+ if (!dmaHeapHandle_.isValid())
+--
+2.43.0
+
diff --git a/nix/pkgs/libcamera/0004-libcamera-internal-Move-SharedMemObject-class-to-a-c.patch b/nix/pkgs/libcamera/0004-libcamera-internal-Move-SharedMemObject-class-to-a-c.patch
new file mode 100644
index 0000000..0222707
--- /dev/null
+++ b/nix/pkgs/libcamera/0004-libcamera-internal-Move-SharedMemObject-class-to-a-c.patch
@@ -0,0 +1,57 @@
+From 006a4a31a6803e92ec67f48b66da2cdff8b2f6ab Mon Sep 17 00:00:00 2001
+From: Andrey Konovalov <andrey.konovalov@linaro.org>
+Date: Sun, 29 Oct 2023 15:56:48 +0300
+Subject: [PATCH 04/25] libcamera: internal: Move SharedMemObject class to a
+ common directory
+
+Move SharedMemObject class out of RPi namespace and put it into
+include/libcamera/internal so that everyone could use it.
+
+Signed-off-by: Andrey Konovalov <andrey.konovalov@linaro.org>
+Signed-off-by: Hans de Goede <hdegoede@redhat.com>
+Tested-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org> # sc8280xp Lenovo x13s
+Tested-by: Pavel Machek <pavel@ucw.cz>
+---
+ include/libcamera/internal/meson.build | 1 +
+ .../common => include/libcamera/internal}/shared_mem_object.h | 4 ----
+ 2 files changed, 1 insertion(+), 4 deletions(-)
+ rename {src/libcamera/pipeline/rpi/common => include/libcamera/internal}/shared_mem_object.h (98%)
+
+diff --git a/include/libcamera/internal/meson.build b/include/libcamera/internal/meson.build
+index 33eb0fb3..5807dfd9 100644
+--- a/include/libcamera/internal/meson.build
++++ b/include/libcamera/internal/meson.build
+@@ -39,6 +39,7 @@ libcamera_internal_headers = files([
+ 'process.h',
+ 'pub_key.h',
+ 'request.h',
++ 'shared_mem_object.h',
+ 'source_paths.h',
+ 'sysfs.h',
+ 'v4l2_device.h',
+diff --git a/src/libcamera/pipeline/rpi/common/shared_mem_object.h b/include/libcamera/internal/shared_mem_object.h
+similarity index 98%
+rename from src/libcamera/pipeline/rpi/common/shared_mem_object.h
+rename to include/libcamera/internal/shared_mem_object.h
+index aa56c220..bfb639ee 100644
+--- a/src/libcamera/pipeline/rpi/common/shared_mem_object.h
++++ b/include/libcamera/internal/shared_mem_object.h
+@@ -19,8 +19,6 @@
+
+ namespace libcamera {
+
+-namespace RPi {
+-
+ template<class T>
+ class SharedMemObject
+ {
+@@ -123,6 +121,4 @@ private:
+ T *obj_;
+ };
+
+-} /* namespace RPi */
+-
+ } /* namespace libcamera */
+--
+2.43.0
+
diff --git a/nix/pkgs/libcamera/0005-libcamera-internal-Document-the-SharedMemObject-clas.patch b/nix/pkgs/libcamera/0005-libcamera-internal-Document-the-SharedMemObject-clas.patch
new file mode 100644
index 0000000..a20d270
--- /dev/null
+++ b/nix/pkgs/libcamera/0005-libcamera-internal-Document-the-SharedMemObject-clas.patch
@@ -0,0 +1,139 @@
+From cb9ff82efd82af8ae26b2aca4183928c74f7ef34 Mon Sep 17 00:00:00 2001
+From: Dennis Bonke <admin@dennisbonke.com>
+Date: Wed, 20 Dec 2023 16:22:29 +0100
+Subject: [PATCH 05/25] libcamera: internal: Document the SharedMemObject class
+
+Document the SharedMemObject class.
+
+Signed-off-by: Dennis Bonke <admin@dennisbonke.com>
+Signed-off-by: Hans de Goede <hdegoede@redhat.com>
+Tested-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org> # sc8280xp Lenovo x13s
+Tested-by: Pavel Machek <pavel@ucw.cz>
+---
+ .../libcamera/internal/shared_mem_object.h | 53 +++++++++++++++++++
+ 1 file changed, 53 insertions(+)
+
+diff --git a/include/libcamera/internal/shared_mem_object.h b/include/libcamera/internal/shared_mem_object.h
+index bfb639ee..e862ce48 100644
+--- a/include/libcamera/internal/shared_mem_object.h
++++ b/include/libcamera/internal/shared_mem_object.h
+@@ -19,10 +19,20 @@
+
+ namespace libcamera {
+
++/**
++ * \class SharedMemObject
++ * \brief Helper class for shared memory allocations.
++ *
++ * Takes a template T which is used to indicate the
++ * data type of the object stored.
++ */
+ template<class T>
+ class SharedMemObject
+ {
+ public:
++ /**
++ * \brief The size of the object that is going to be stored here.
++ */
+ static constexpr std::size_t SIZE = sizeof(T);
+
+ SharedMemObject()
+@@ -30,6 +40,11 @@ public:
+ {
+ }
+
++ /**
++ * \brief Contstructor for the SharedMemObject.
++ * \param[in] name The requested name.
++ * \param[in] args Any additional args.
++ */
+ template<class... Args>
+ SharedMemObject(const std::string &name, Args &&...args)
+ : name_(name), obj_(nullptr)
+@@ -57,6 +72,10 @@ public:
+ obj_ = new (mem) T(std::forward<Args>(args)...);
+ }
+
++ /**
++ * \brief Move constructor for SharedMemObject.
++ * \param[in] rhs The object to move.
++ */
+ SharedMemObject(SharedMemObject<T> &&rhs)
+ {
+ this->name_ = std::move(rhs.name_);
+@@ -76,6 +95,10 @@ public:
+ /* Make SharedMemObject non-copyable for now. */
+ LIBCAMERA_DISABLE_COPY(SharedMemObject)
+
++ /**
++ * \brief Operator= for SharedMemObject.
++ * \param[in] rhs The SharedMemObject object to take the data from.
++ */
+ SharedMemObject<T> &operator=(SharedMemObject<T> &&rhs)
+ {
+ this->name_ = std::move(rhs.name_);
+@@ -85,31 +108,61 @@ public:
+ return *this;
+ }
+
++ /**
++ * \brief Operator-> for SharedMemObject.
++ *
++ * \return the object.
++ */
+ T *operator->()
+ {
+ return obj_;
+ }
+
++ /**
++ * \brief Operator-> for SharedMemObject.
++ *
++ * \return the object.
++ */
+ const T *operator->() const
+ {
+ return obj_;
+ }
+
++ /**
++ * \brief Operator* for SharedMemObject.
++ *
++ * \return the object.
++ */
+ T &operator*()
+ {
+ return *obj_;
+ }
+
++ /**
++ * \brief Operator* for SharedMemObject.
++ *
++ * \return the object.
++ */
+ const T &operator*() const
+ {
+ return *obj_;
+ }
+
++ /**
++ * \brief Gets the file descriptor for the underlaying storage file.
++ *
++ * \return the file descriptor.
++ */
+ const SharedFD &fd() const
+ {
+ return fd_;
+ }
+
++ /**
++ * \brief Operator bool() for SharedMemObject.
++ *
++ * \return true if the object is not null, false otherwise.
++ */
+ explicit operator bool() const
+ {
+ return !!obj_;
+--
+2.43.0
+
diff --git a/nix/pkgs/libcamera/0006-libcamera-introduce-SoftwareIsp-class.patch b/nix/pkgs/libcamera/0006-libcamera-introduce-SoftwareIsp-class.patch
new file mode 100644
index 0000000..ebda98d
--- /dev/null
+++ b/nix/pkgs/libcamera/0006-libcamera-introduce-SoftwareIsp-class.patch
@@ -0,0 +1,354 @@
+From 3fa62a8e2f34c9794ba67e2565db8fef22938fa4 Mon Sep 17 00:00:00 2001
+From: Andrey Konovalov <andrey.konovalov@linaro.org>
+Date: Sun, 22 Oct 2023 17:49:32 +0300
+Subject: [PATCH 06/25] libcamera: introduce SoftwareIsp class
+
+Doxygen documentation by Dennis Bonke.
+
+Co-authored-by: Dennis Bonke <admin@dennisbonke.com>
+Signed-off-by: Dennis Bonke <admin@dennisbonke.com>
+Signed-off-by: Andrey Konovalov <andrey.konovalov@linaro.org>
+Signed-off-by: Hans de Goede <hdegoede@redhat.com>
+Tested-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org> # sc8280xp Lenovo x13s
+Tested-by: Pavel Machek <pavel@ucw.cz>
+---
+ include/libcamera/internal/meson.build | 1 +
+ include/libcamera/internal/software_isp.h | 231 ++++++++++++++++++++++
+ src/libcamera/meson.build | 1 +
+ src/libcamera/software_isp.cpp | 62 ++++++
+ 4 files changed, 295 insertions(+)
+ create mode 100644 include/libcamera/internal/software_isp.h
+ create mode 100644 src/libcamera/software_isp.cpp
+
+diff --git a/include/libcamera/internal/meson.build b/include/libcamera/internal/meson.build
+index 5807dfd9..1325941d 100644
+--- a/include/libcamera/internal/meson.build
++++ b/include/libcamera/internal/meson.build
+@@ -40,6 +40,7 @@ libcamera_internal_headers = files([
+ 'pub_key.h',
+ 'request.h',
+ 'shared_mem_object.h',
++ 'software_isp.h',
+ 'source_paths.h',
+ 'sysfs.h',
+ 'v4l2_device.h',
+diff --git a/include/libcamera/internal/software_isp.h b/include/libcamera/internal/software_isp.h
+new file mode 100644
+index 00000000..42ff48ec
+--- /dev/null
++++ b/include/libcamera/internal/software_isp.h
+@@ -0,0 +1,231 @@
++/* SPDX-License-Identifier: LGPL-2.1-or-later */
++/*
++ * Copyright (C) 2023, Linaro Ltd
++ *
++ * software_isp.h - Interface for a software implementation of an ISP
++ */
++
++#pragma once
++
++#include <functional>
++#include <initializer_list>
++#include <map>
++#include <memory>
++#include <string>
++#include <tuple>
++#include <vector>
++
++#include <libcamera/base/class.h>
++#include <libcamera/base/log.h>
++#include <libcamera/base/signal.h>
++
++#include <libcamera/geometry.h>
++
++#include "libcamera/internal/pipeline_handler.h"
++
++namespace libcamera {
++
++class FrameBuffer;
++class PixelFormat;
++struct StreamConfiguration;
++
++LOG_DECLARE_CATEGORY(SoftwareIsp)
++
++/**
++ * \brief Base class for the Software ISP.
++ *
++ * Base class of the SoftwareIsp interface.
++ */
++class SoftwareIsp
++{
++public:
++ /**
++ * \brief Constructor for the SoftwareIsp object.
++ * \param[in] pipe The pipeline handler in use.
++ * \param[in] sensorControls The sensor controls.
++ */
++ SoftwareIsp(PipelineHandler *pipe, const ControlInfoMap &sensorControls);
++ virtual ~SoftwareIsp();
++
++ /**
++ * \brief Load a configuration from a file.
++ * \param[in] filename The file to load from.
++ *
++ * \return 0 on success.
++ */
++ virtual int loadConfiguration(const std::string &filename) = 0;
++
++ /**
++ * \brief Gets if there is a valid debayer object.
++ *
++ * \returns true if there is, false otherwise.
++ */
++ virtual bool isValid() const = 0;
++
++ /**
++ * \brief Get the supported output formats.
++ * \param[in] input The input format.
++ *
++ * \return all supported output formats or an empty vector if there are none.
++ */
++ virtual std::vector<PixelFormat> formats(PixelFormat input) = 0;
++
++ /**
++ * \brief Get the supported output sizes for the given input format and size.
++ * \param[in] inputFormat The input format.
++ * \param[in] inputSize The input size.
++ *
++ * \return The valid size ranges or an empty range if there are none.
++ */
++ virtual SizeRange sizes(PixelFormat inputFormat, const Size &inputSize) = 0;
++
++ /**
++ * \brief Get the stride and the frame size.
++ * \param[in] pixelFormat The output format.
++ * \param[in] size The output size.
++ *
++ * \return a tuple of the stride and the frame size, or a tuple with 0,0 if there is no valid output config.
++ */
++ virtual std::tuple<unsigned int, unsigned int>
++ strideAndFrameSize(const PixelFormat &pixelFormat, const Size &size) = 0;
++
++ /**
++ * \brief Configure the SwIspSimple object according to the passed in parameters.
++ * \param[in] inputCfg The input configuration.
++ * \param[in] outputCfgs The output configurations.
++ * \param[in] sensorControls The sensor controls.
++ *
++ * \return 0 on success, a negative errno on failure.
++ */
++ virtual int configure(const StreamConfiguration &inputCfg,
++ const std::vector<std::reference_wrapper<StreamConfiguration>> &outputCfgs,
++ const ControlInfoMap &sensorControls) = 0;
++
++ /**
++ * \brief Exports the buffers for use in processing.
++ * \param[in] output The number of outputs requested.
++ * \param[in] count The number of planes.
++ * \param[out] buffers The exported buffers.
++ *
++ * \return count when successful, a negative return value if an error occurred.
++ */
++ virtual int exportBuffers(unsigned int output, unsigned int count,
++ std::vector<std::unique_ptr<FrameBuffer>> *buffers) = 0;
++
++ /**
++ * \brief Starts the Software ISP worker.
++ *
++ * \return 0 on success, any other value indicates an error.
++ */
++ virtual int start() = 0;
++
++ /**
++ * \brief Stops the Software ISP worker.
++ */
++ virtual void stop() = 0;
++
++ /**
++ * \brief Queues buffers for processing.
++ * \param[in] input The input framebuffer.
++ * \param[in] outputs The output framebuffers.
++ *
++ * \return 0 on success, a negative errno on failure
++ */
++ virtual int queueBuffers(FrameBuffer *input,
++ const std::map<unsigned int, FrameBuffer *> &outputs) = 0;
++
++ /**
++ * \brief Process the statistics gathered.
++ * \param[in] sensorControls The sensor controls.
++ */
++ virtual void processStats(const ControlList &sensorControls) = 0; // rather merge with queueBuffers()?
++
++ /**
++ * \brief Get the signal for when the sensor controls are set.
++ *
++ * \return The control list of the sensor controls.
++ */
++ virtual Signal<const ControlList &> &getSignalSetSensorControls() = 0;
++
++ /**
++ * \brief Signals that the input buffer is ready.
++ */
++ Signal<FrameBuffer *> inputBufferReady;
++ /**
++ * \brief Signals that the output buffer is ready.
++ */
++ Signal<FrameBuffer *> outputBufferReady;
++
++ /**
++ * \brief Signals that the ISP stats are ready.
++ *
++ * The int parameter isn't actually used.
++ */
++ Signal<int> ispStatsReady;
++};
++
++/**
++ * \brief Base class for the Software ISP Factory.
++ *
++ * Base class of the SoftwareIsp Factory.
++ */
++class SoftwareIspFactoryBase
++{
++public:
++ SoftwareIspFactoryBase();
++ virtual ~SoftwareIspFactoryBase() = default;
++
++ /**
++ * \brief Creates a SoftwareIsp object.
++ * \param[in] pipe The pipeline handler in use.
++ * \param[in] sensorControls The sensor controls.
++ *
++ * \return An unique pointer to the created SoftwareIsp object.
++ */
++ static std::unique_ptr<SoftwareIsp> create(PipelineHandler *pipe,
++ const ControlInfoMap &sensorControls);
++ /**
++ * \brief Gives back a pointer to the factory.
++ *
++ * \return A static pointer to the factory instance.
++ */
++ static SoftwareIspFactoryBase *&factory();
++
++private:
++ LIBCAMERA_DISABLE_COPY_AND_MOVE(SoftwareIspFactoryBase)
++
++ static void registerType(SoftwareIspFactoryBase *factory);
++ virtual std::unique_ptr<SoftwareIsp> createInstance(PipelineHandler *pipe,
++ const ControlInfoMap &sensorControls) const = 0;
++};
++
++/**
++ * \brief Implementation for the Software ISP Factory.
++ */
++template<typename _SoftwareIsp>
++class SoftwareIspFactory : public SoftwareIspFactoryBase
++{
++public:
++ SoftwareIspFactory()
++ : SoftwareIspFactoryBase()
++ {
++ }
++
++ /**
++ * \brief Creates an instance of a SoftwareIsp object.
++ * \param[in] pipe The pipeline handler in use.
++ * \param[in] sensorControls The sensor controls.
++ *
++ * \return An unique pointer to the created SoftwareIsp object.
++ */
++ std::unique_ptr<SoftwareIsp> createInstance(PipelineHandler *pipe,
++ const ControlInfoMap &sensorControls) const override
++ {
++ return std::make_unique<_SoftwareIsp>(pipe, sensorControls);
++ }
++};
++
++#define REGISTER_SOFTWAREISP(softwareIsp) \
++ static SoftwareIspFactory<softwareIsp> global_##softwareIsp##Factory;
++
++} /* namespace libcamera */
+diff --git a/src/libcamera/meson.build b/src/libcamera/meson.build
+index 3c5e43df..86494663 100644
+--- a/src/libcamera/meson.build
++++ b/src/libcamera/meson.build
+@@ -41,6 +41,7 @@ libcamera_sources = files([
+ 'process.cpp',
+ 'pub_key.cpp',
+ 'request.cpp',
++ 'software_isp.cpp',
+ 'source_paths.cpp',
+ 'stream.cpp',
+ 'sysfs.cpp',
+diff --git a/src/libcamera/software_isp.cpp b/src/libcamera/software_isp.cpp
+new file mode 100644
+index 00000000..2ff97d70
+--- /dev/null
++++ b/src/libcamera/software_isp.cpp
+@@ -0,0 +1,62 @@
++/* SPDX-License-Identifier: LGPL-2.1-or-later */
++/*
++ * Copyright (C) 2023, Linaro Ltd
++ *
++ * software_isp.cpp - Interface for a software implementation of an ISP
++ */
++
++#include "libcamera/internal/software_isp.h"
++
++#include <libcamera/base/log.h>
++
++namespace libcamera {
++
++LOG_DEFINE_CATEGORY(SoftwareIsp)
++
++SoftwareIsp::SoftwareIsp([[maybe_unused]] PipelineHandler *pipe,
++ [[maybe_unused]] const ControlInfoMap &sensorControls)
++{
++}
++
++SoftwareIsp::~SoftwareIsp()
++{
++}
++
++/* SoftwareIspFactoryBase */
++
++SoftwareIspFactoryBase::SoftwareIspFactoryBase()
++{
++ registerType(this);
++}
++
++void SoftwareIspFactoryBase::registerType(SoftwareIspFactoryBase *factory)
++{
++ SoftwareIspFactoryBase *&registered =
++ SoftwareIspFactoryBase::factory();
++
++ ASSERT(!registered && factory);
++ registered = factory;
++}
++
++SoftwareIspFactoryBase *&SoftwareIspFactoryBase::factory()
++{
++ static SoftwareIspFactoryBase *factory;
++ return factory;
++}
++
++std::unique_ptr<SoftwareIsp>
++SoftwareIspFactoryBase::create(PipelineHandler *pipe,
++ const ControlInfoMap &sensorControls)
++{
++ SoftwareIspFactoryBase *factory = SoftwareIspFactoryBase::factory();
++ if (!factory)
++ return nullptr;
++
++ std::unique_ptr<SoftwareIsp> swIsp = factory->createInstance(pipe, sensorControls);
++ if (swIsp->isValid())
++ return swIsp;
++
++ return nullptr;
++}
++
++} /* namespace libcamera */
+--
+2.43.0
+
diff --git a/nix/pkgs/libcamera/0007-libcamera-software_isp-Add-SwStats-base-class.patch b/nix/pkgs/libcamera/0007-libcamera-software_isp-Add-SwStats-base-class.patch
new file mode 100644
index 0000000..9d6f2ac
--- /dev/null
+++ b/nix/pkgs/libcamera/0007-libcamera-software_isp-Add-SwStats-base-class.patch
@@ -0,0 +1,382 @@
+From ca3bb6ddf5307537aa05e43d3ec1ff7ffdc0efed Mon Sep 17 00:00:00 2001
+From: Hans de Goede <hdegoede@redhat.com>
+Date: Thu, 7 Dec 2023 13:30:27 +0100
+Subject: [PATCH 07/25] libcamera: software_isp: Add SwStats base class
+
+Add a virtual base class for CPU based software statistics gathering
+implementations.
+
+The idea is for the implementations to offer a configure function +
+functions to gather statistics on a line by line basis. This allows
+CPU based software debayering to call into interlace debayering and
+statistics gathering on a line by line bases while the input data
+is still hot in the cache.
+
+This base class also allows the user of an implementation to specify
+a window over which to gather statistics instead of processing the
+whole frame; and it allows the implementation to choose to only
+process 1/2, 1/4th, etc. of the lines instead of processing all
+lines (in the window) by setting y_skip_mask_ from configure().
+Skipping columns is left up the line-processing functions provided
+by the implementation.
+
+Doxygen documentation by Dennis Bonke.
+
+Co-authored-by: Dennis Bonke <admin@dennisbonke.com>
+Signed-off-by: Dennis Bonke <admin@dennisbonke.com>
+Co-authored-by: Andrey Konovalov <andrey.konovalov@linaro.org>
+Signed-off-by: Andrey Konovalov <andrey.konovalov@linaro.org>
+Signed-off-by: Hans de Goede <hdegoede@redhat.com>
+Tested-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org> # sc8280xp Lenovo x13s
+Tested-by: Pavel Machek <pavel@ucw.cz>
+---
+ include/libcamera/internal/meson.build | 1 +
+ .../internal/software_isp/meson.build | 6 +
+ .../internal/software_isp/swisp_stats.h | 34 +++
+ .../libcamera/internal/software_isp/swstats.h | 215 ++++++++++++++++++
+ src/libcamera/meson.build | 1 +
+ src/libcamera/software_isp/meson.build | 5 +
+ src/libcamera/software_isp/swstats.cpp | 22 ++
+ 7 files changed, 284 insertions(+)
+ create mode 100644 include/libcamera/internal/software_isp/meson.build
+ create mode 100644 include/libcamera/internal/software_isp/swisp_stats.h
+ create mode 100644 include/libcamera/internal/software_isp/swstats.h
+ create mode 100644 src/libcamera/software_isp/meson.build
+ create mode 100644 src/libcamera/software_isp/swstats.cpp
+
+diff --git a/include/libcamera/internal/meson.build b/include/libcamera/internal/meson.build
+index 1325941d..caa533c4 100644
+--- a/include/libcamera/internal/meson.build
++++ b/include/libcamera/internal/meson.build
+@@ -51,3 +51,4 @@ libcamera_internal_headers = files([
+ ])
+
+ subdir('converter')
++subdir('software_isp')
+diff --git a/include/libcamera/internal/software_isp/meson.build b/include/libcamera/internal/software_isp/meson.build
+new file mode 100644
+index 00000000..1c43acc4
+--- /dev/null
++++ b/include/libcamera/internal/software_isp/meson.build
+@@ -0,0 +1,6 @@
++# SPDX-License-Identifier: CC0-1.0
++
++libcamera_internal_headers += files([
++ 'swisp_stats.h',
++ 'swstats.h',
++])
+diff --git a/include/libcamera/internal/software_isp/swisp_stats.h b/include/libcamera/internal/software_isp/swisp_stats.h
+new file mode 100644
+index 00000000..07ba7d6a
+--- /dev/null
++++ b/include/libcamera/internal/software_isp/swisp_stats.h
+@@ -0,0 +1,34 @@
++/* SPDX-License-Identifier: LGPL-2.1-or-later */
++/*
++ * Copyright (C) 2023, Linaro Ltd
++ *
++ * swisp_stats.h - Statistics data format used by the software ISP and software IPA
++ */
++
++#pragma once
++
++namespace libcamera {
++
++/**
++ * \brief Struct that holds the statistics for the Software ISP.
++ */
++struct SwIspStats {
++ /**
++ * \brief Holds the sum of all sampled red pixels.
++ */
++ unsigned long sumR_;
++ /**
++ * \brief Holds the sum of all sampled green pixels.
++ */
++ unsigned long sumG_;
++ /**
++ * \brief Holds the sum of all sampled blue pixels.
++ */
++ unsigned long sumB_;
++ /**
++ * \brief A histogram of luminance values.
++ */
++ unsigned int y_histogram[16];
++};
++
++} /* namespace libcamera */
+diff --git a/include/libcamera/internal/software_isp/swstats.h b/include/libcamera/internal/software_isp/swstats.h
+new file mode 100644
+index 00000000..dcac7064
+--- /dev/null
++++ b/include/libcamera/internal/software_isp/swstats.h
+@@ -0,0 +1,215 @@
++/* SPDX-License-Identifier: LGPL-2.1-or-later */
++/*
++ * Copyright (C) 2023, Linaro Ltd
++ * Copyright (C) 2023, Red Hat Inc.
++ *
++ * Authors:
++ * Hans de Goede <hdegoede@redhat.com>
++ *
++ * swstats.h - software statistics base class
++ */
++
++#pragma once
++
++#include <stdint.h>
++
++#include <libcamera/base/log.h>
++#include <libcamera/base/signal.h>
++
++#include <libcamera/geometry.h>
++
++namespace libcamera {
++
++class PixelFormat;
++struct SharedFD;
++struct StreamConfiguration;
++
++LOG_DECLARE_CATEGORY(SwStats)
++
++/**
++ * \class SwStats
++ * \brief Base class for the software ISP statistics.
++ *
++ * Base class for the software ISP statistics.
++ */
++class SwStats
++{
++public:
++ virtual ~SwStats() = 0;
++
++ /**
++ * \brief Gets wether the statistics object is valid.
++ *
++ * \return true if it's valid, false otherwise.
++ */
++ virtual bool isValid() const = 0;
++
++ /**
++ * \brief Configure the statistics object for the passed in input format.
++ * \param[in] inputCfg The input format
++ *
++ * \return 0 on success, a negative errno value on failure.
++ */
++ virtual int configure(const StreamConfiguration &inputCfg) = 0;
++
++ /**
++ * \brief Get the file descriptor for the statistics.
++ *
++ * \return the file descriptor
++ */
++ virtual const SharedFD &getStatsFD() = 0;
++
++protected:
++ /**
++ * \brief Called when there is data to get statistics from.
++ * \param[in] src The input data
++ */
++ typedef void (SwStats::*statsProcessFn)(const uint8_t *src[]);
++ /**
++ * \brief Called when the statistics gathering is done or when a new frame starts.
++ */
++ typedef void (SwStats::*statsVoidFn)();
++
++ /* Variables set by configure(), used every line */
++ /**
++ * \brief The function called when a line is ready for statistics processing.
++ *
++ * Used for line 0 and 1, repeating if there isn't a 3rd and a 4th line in the bayer order.
++ */
++ statsProcessFn stats0_;
++ /**
++ * \brief The function called when a line is ready for statistics processing.
++ *
++ * Used for line 3 and 4, only needed if the bayer order has 4 different lines.
++ */
++ statsProcessFn stats2_;
++
++ /**
++ * \brief The memory used per pixel in bits.
++ */
++ unsigned int bpp_;
++ /**
++ * \brief Skip lines where this bitmask is set in y.
++ */
++ unsigned int y_skip_mask_;
++
++ /**
++ * \brief Statistics window, set by setWindow(), used ever line.
++ */
++ Rectangle window_;
++
++ /**
++ * \brief The function called at the start of a frame.
++ */
++ statsVoidFn startFrame_;
++ /**
++ * \brief The function called at the end of a frame.
++ */
++ statsVoidFn finishFrame_;
++ /**
++ * \brief The size of the bayer pattern.
++ */
++ Size patternSize_;
++ /**
++ * \brief The offset of x, applied to window_.x for bayer variants.
++ *
++ * This can either be 0 or 1.
++ */
++ unsigned int x_shift_;
++
++public:
++ /**
++ * \brief Get the pattern size.
++ *
++ * For some input-formats, e.g. Bayer data, processing is done multiple lines
++ * and/or columns at a time. Get width and height at which the (bayer) pattern
++ * repeats. Window values are rounded down to a multiple of this and the height
++ * also indicates if processLine2() should be called or not.
++ * This may only be called after a successful configure() call.
++ *
++ * \return the pattern size.
++ */
++ const Size &patternSize() { return patternSize_; }
++
++ /**
++ * \brief Specify window coordinates over which to gather statistics.
++ * \param[in] window The window object.
++ */
++ void setWindow(Rectangle window)
++ {
++ window_ = window;
++
++ window_.x &= ~(patternSize_.width - 1);
++ window_.x += x_shift_;
++ window_.y &= ~(patternSize_.height - 1);
++
++ /* width_ - x_shift_ to make sure the window fits */
++ window_.width -= x_shift_;
++ window_.width &= ~(patternSize_.width - 1);
++ window_.height &= ~(patternSize_.height - 1);
++ }
++
++ /**
++ * \brief Reset state to start statistics gathering for a new frame.
++ *
++ * This may only be called after a successful setWindow() call.
++ */
++ void startFrame()
++ {
++ (this->*startFrame_)();
++ }
++
++ /**
++ * \brief Process line 0.
++ * \param[in] y The y coordinate.
++ * \param[in] src The input data.
++ *
++ * This function processes line 0 for input formats with patternSize height == 1.
++ * It'll process line 0 and 1 for input formats with patternSize height >= 2.
++ * This function may only be called after a successful setWindow() call.
++ */
++ void processLine0(unsigned int y, const uint8_t *src[])
++ {
++ if ((y & y_skip_mask_) || y < (unsigned int)window_.y ||
++ y >= (window_.y + window_.height))
++ return;
++
++ (this->*stats0_)(src);
++ }
++
++ /**
++ * \brief Process line 2 and 3.
++ * \param[in] y The y coordinate.
++ * \param[in] src The input data.
++ *
++ * This function processes line 2 and 3 for input formats with patternSize height == 4.
++ * This function may only be called after a successful setWindow() call.
++ */
++ void processLine2(unsigned int y, const uint8_t *src[])
++ {
++ if ((y & y_skip_mask_) || y < (unsigned int)window_.y ||
++ y >= (window_.y + window_.height))
++ return;
++
++ (this->*stats2_)(src);
++ }
++
++ /**
++ * \brief Finish statistics calculation for the current frame.
++ *
++ * This may only be called after a successful setWindow() call.
++ */
++ void finishFrame()
++ {
++ (this->*finishFrame_)();
++ }
++
++ /**
++ * \brief Signals that the statistics are ready.
++ *
++ * The int parameter isn't actually used.
++ */
++ Signal<int> statsReady;
++};
++
++} /* namespace libcamera */
+diff --git a/src/libcamera/meson.build b/src/libcamera/meson.build
+index 86494663..3d63e8a2 100644
+--- a/src/libcamera/meson.build
++++ b/src/libcamera/meson.build
+@@ -71,6 +71,7 @@ subdir('converter')
+ subdir('ipa')
+ subdir('pipeline')
+ subdir('proxy')
++subdir('software_isp')
+
+ null_dep = dependency('', required : false)
+
+diff --git a/src/libcamera/software_isp/meson.build b/src/libcamera/software_isp/meson.build
+new file mode 100644
+index 00000000..9359075d
+--- /dev/null
++++ b/src/libcamera/software_isp/meson.build
+@@ -0,0 +1,5 @@
++# SPDX-License-Identifier: CC0-1.0
++
++libcamera_sources += files([
++ 'swstats.cpp',
++])
+diff --git a/src/libcamera/software_isp/swstats.cpp b/src/libcamera/software_isp/swstats.cpp
+new file mode 100644
+index 00000000..e65a7ada
+--- /dev/null
++++ b/src/libcamera/software_isp/swstats.cpp
+@@ -0,0 +1,22 @@
++/* SPDX-License-Identifier: LGPL-2.1-or-later */
++/*
++ * Copyright (C) 2023, Linaro Ltd
++ * Copyright (C) 2023, Red Hat Inc.
++ *
++ * Authors:
++ * Hans de Goede <hdegoede@redhat.com>
++ *
++ * swstats.cpp - software statistics base class
++ */
++
++#include "libcamera/internal/software_isp/swstats.h"
++
++namespace libcamera {
++
++LOG_DEFINE_CATEGORY(SwStats)
++
++SwStats::~SwStats()
++{
++}
++
++} /* namespace libcamera */
+--
+2.43.0
+
diff --git a/nix/pkgs/libcamera/0008-libcamera-software_isp-Add-SwStatsCpu-class.patch b/nix/pkgs/libcamera/0008-libcamera-software_isp-Add-SwStatsCpu-class.patch
new file mode 100644
index 0000000..d48eadd
--- /dev/null
+++ b/nix/pkgs/libcamera/0008-libcamera-software_isp-Add-SwStatsCpu-class.patch
@@ -0,0 +1,272 @@
+From c1c43445cd4408010e500fe9d6b6424c77bcf75d Mon Sep 17 00:00:00 2001
+From: Hans de Goede <hdegoede@redhat.com>
+Date: Fri, 8 Dec 2023 12:50:57 +0100
+Subject: [PATCH 08/25] libcamera: software_isp: Add SwStatsCpu class
+
+Add a CPU based SwStats implementation for SoftwareISP / SoftIPA use.
+
+Co-authored-by: Andrey Konovalov <andrey.konovalov@linaro.org>
+Signed-off-by: Andrey Konovalov <andrey.konovalov@linaro.org>
+Co-authored-by: Pavel Machek <pavel@ucw.cz>
+Signed-off-by: Pavel Machek <pavel@ucw.cz>
+Co-authored-by: Dennis Bonke <admin@dennisbonke.com>
+Signed-off-by: Dennis Bonke <admin@dennisbonke.com>
+Co-authored-by: Marttico <g.martti@gmail.com>
+Signed-off-by: Marttico <g.martti@gmail.com>
+Co-authored-by: Toon Langendam <t.langendam@gmail.com>
+Signed-off-by: Toon Langendam <t.langendam@gmail.com>
+Signed-off-by: Hans de Goede <hdegoede@redhat.com>
+Tested-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org> # sc8280xp Lenovo x13s
+Tested-by: Pavel Machek <pavel@ucw.cz>
+---
+ .../internal/software_isp/meson.build | 1 +
+ .../internal/software_isp/swstats_cpu.h | 44 +++++
+ src/libcamera/software_isp/meson.build | 1 +
+ src/libcamera/software_isp/swstats_cpu.cpp | 164 ++++++++++++++++++
+ 4 files changed, 210 insertions(+)
+ create mode 100644 include/libcamera/internal/software_isp/swstats_cpu.h
+ create mode 100644 src/libcamera/software_isp/swstats_cpu.cpp
+
+diff --git a/include/libcamera/internal/software_isp/meson.build b/include/libcamera/internal/software_isp/meson.build
+index 1c43acc4..1d9e4018 100644
+--- a/include/libcamera/internal/software_isp/meson.build
++++ b/include/libcamera/internal/software_isp/meson.build
+@@ -3,4 +3,5 @@
+ libcamera_internal_headers += files([
+ 'swisp_stats.h',
+ 'swstats.h',
++ 'swstats_cpu.h',
+ ])
+diff --git a/include/libcamera/internal/software_isp/swstats_cpu.h b/include/libcamera/internal/software_isp/swstats_cpu.h
+new file mode 100644
+index 00000000..8bb86e98
+--- /dev/null
++++ b/include/libcamera/internal/software_isp/swstats_cpu.h
+@@ -0,0 +1,44 @@
++/* SPDX-License-Identifier: LGPL-2.1-or-later */
++/*
++ * Copyright (C) 2023, Linaro Ltd
++ * Copyright (C) 2023, Red Hat Inc.
++ *
++ * Authors:
++ * Hans de Goede <hdegoede@redhat.com>
++ *
++ * swstats_cpu.h - CPU based software statistics implementation
++ */
++
++#pragma once
++
++#include "libcamera/internal/shared_mem_object.h"
++#include "libcamera/internal/software_isp/swisp_stats.h"
++#include "libcamera/internal/software_isp/swstats.h"
++
++namespace libcamera {
++
++/**
++ * \class SwStatsCpu
++ * \brief Implementation for the Software statistics on the CPU.
++ */
++class SwStatsCpu : public SwStats
++{
++public:
++ SwStatsCpu();
++ ~SwStatsCpu() { }
++
++ bool isValid() const { return sharedStats_.fd().isValid(); }
++ const SharedFD &getStatsFD() { return sharedStats_.fd(); }
++ int configure(const StreamConfiguration &inputCfg);
++private:
++ void statsBGGR10PLine0(const uint8_t *src[]);
++ void statsGBRG10PLine0(const uint8_t *src[]);
++ void resetStats(void);
++ void finishStats(void);
++
++ SharedMemObject<SwIspStats> sharedStats_;
++ SwIspStats stats_;
++ bool swap_lines_;
++};
++
++} /* namespace libcamera */
+diff --git a/src/libcamera/software_isp/meson.build b/src/libcamera/software_isp/meson.build
+index 9359075d..d31c6217 100644
+--- a/src/libcamera/software_isp/meson.build
++++ b/src/libcamera/software_isp/meson.build
+@@ -2,4 +2,5 @@
+
+ libcamera_sources += files([
+ 'swstats.cpp',
++ 'swstats_cpu.cpp',
+ ])
+diff --git a/src/libcamera/software_isp/swstats_cpu.cpp b/src/libcamera/software_isp/swstats_cpu.cpp
+new file mode 100644
+index 00000000..59453d07
+--- /dev/null
++++ b/src/libcamera/software_isp/swstats_cpu.cpp
+@@ -0,0 +1,164 @@
++/* SPDX-License-Identifier: LGPL-2.1-or-later */
++/*
++ * Copyright (C) 2023, Linaro Ltd
++ * Copyright (C) 2023, Red Hat Inc.
++ *
++ * Authors:
++ * Hans de Goede <hdegoede@redhat.com>
++ *
++ * swstats_cpu.cpp - CPU based software statistics implementation
++ */
++
++#include "libcamera/internal/software_isp/swstats_cpu.h"
++
++#include <libcamera/base/log.h>
++
++#include <libcamera/stream.h>
++
++#include "libcamera/internal/bayer_format.h"
++
++namespace libcamera {
++
++SwStatsCpu::SwStatsCpu()
++ : SwStats()
++{
++ sharedStats_ = SharedMemObject<SwIspStats>("softIsp_stats");
++ if (!sharedStats_.fd().isValid())
++ LOG(SwStats, Error)
++ << "Failed to create shared memory for statistics";
++}
++
++/* for brightness values in the 0 to 255 range: */
++static const unsigned int BRIGHT_LVL = 200U << 8;
++static const unsigned int TOO_BRIGHT_LVL = 240U << 8;
++
++static const unsigned int RED_Y_MUL = 77; /* 0.30 * 256 */
++static const unsigned int GREEN_Y_MUL = 150; /* 0.59 * 256 */
++static const unsigned int BLUE_Y_MUL = 29; /* 0.11 * 256 */
++
++#define SWISP_LINARO_START_LINE_STATS(pixel_t) \
++ pixel_t r, g, g2, b; \
++ unsigned int y_val; \
++ \
++ unsigned int sumR = 0; \
++ unsigned int sumG = 0; \
++ unsigned int sumB = 0;
++
++#define SWISP_LINARO_ACCUMULATE_LINE_STATS(div) \
++ sumR += r; \
++ sumG += g; \
++ sumB += b; \
++ \
++ y_val = r * RED_Y_MUL; \
++ y_val += g * GREEN_Y_MUL; \
++ y_val += b * BLUE_Y_MUL; \
++ stats_.y_histogram[y_val / (256 * 16 * (div))]++;
++
++#define SWISP_LINARO_FINISH_LINE_STATS() \
++ stats_.sumR_ += sumR; \
++ stats_.sumG_ += sumG; \
++ stats_.sumB_ += sumB;
++
++static inline __attribute__((always_inline)) void
++statsBayer10P(const int width, const uint8_t *src0, const uint8_t *src1, bool bggr, SwIspStats &stats_)
++{
++ const int width_in_bytes = width * 5 / 4;
++
++ SWISP_LINARO_START_LINE_STATS(uint8_t)
++
++ for (int x = 0; x < width_in_bytes; x += 5) {
++ if (bggr) {
++ /* BGGR */
++ b = src0[x];
++ g = src0[x + 1];
++ g2 = src1[x];
++ r = src1[x + 1];
++ } else {
++ /* GBRG */
++ g = src0[x];
++ b = src0[x + 1];
++ r = src1[x];
++ g2 = src1[x + 1];
++ }
++ g = (g + g2) / 2;
++
++ SWISP_LINARO_ACCUMULATE_LINE_STATS(1)
++ }
++
++ SWISP_LINARO_FINISH_LINE_STATS()
++}
++
++void SwStatsCpu::statsBGGR10PLine0(const uint8_t *src[])
++{
++ const uint8_t *src0 = src[1] + window_.x * 5 / 4;
++ const uint8_t *src1 = src[2] + window_.x * 5 / 4;
++
++ if (swap_lines_)
++ std::swap(src0, src1);
++
++ statsBayer10P(window_.width, src0, src1, true, stats_);
++}
++
++void SwStatsCpu::statsGBRG10PLine0(const uint8_t *src[])
++{
++ const uint8_t *src0 = src[1] + window_.x * 5 / 4;
++ const uint8_t *src1 = src[2] + window_.x * 5 / 4;
++
++ if (swap_lines_)
++ std::swap(src0, src1);
++
++ statsBayer10P(window_.width, src0, src1, false, stats_);
++}
++
++void SwStatsCpu::resetStats(void)
++{
++ stats_.sumR_ = 0;
++ stats_.sumB_ = 0;
++ stats_.sumG_ = 0;
++ std::fill_n(stats_.y_histogram, 16, 0);
++}
++
++void SwStatsCpu::finishStats(void)
++{
++ *sharedStats_ = stats_;
++ statsReady.emit(0);
++}
++
++int SwStatsCpu::configure(const StreamConfiguration &inputCfg)
++{
++ BayerFormat bayerFormat =
++ BayerFormat::fromPixelFormat(inputCfg.pixelFormat);
++
++ startFrame_ = (SwStats::statsVoidFn)&SwStatsCpu::resetStats;
++ finishFrame_ = (SwStats::statsVoidFn)&SwStatsCpu::finishStats;
++
++ if (bayerFormat.bitDepth == 10 &&
++ bayerFormat.packing == BayerFormat::Packing::CSI2) {
++ bpp_ = 10;
++ patternSize_.height = 2;
++ patternSize_.width = 4; /* 5 bytes per *4* pixels */
++ y_skip_mask_ = 0x02; /* Skip every 3th and 4th line */
++ x_shift_ = 0;
++
++ switch (bayerFormat.order) {
++ case BayerFormat::BGGR:
++ case BayerFormat::GRBG:
++ stats0_ = (SwStats::statsProcessFn)&SwStatsCpu::statsBGGR10PLine0;
++ swap_lines_ = bayerFormat.order == BayerFormat::GRBG;
++ return 0;
++ case BayerFormat::GBRG:
++ case BayerFormat::RGGB:
++ stats0_ = (SwStats::statsProcessFn)&SwStatsCpu::statsGBRG10PLine0;
++ swap_lines_ = bayerFormat.order == BayerFormat::RGGB;
++ return 0;
++ default:
++ break;
++ }
++ }
++
++ LOG(SwStats, Info)
++ << "Unsupported input format " << inputCfg.pixelFormat.toString();
++ return -EINVAL;
++}
++
++} /* namespace libcamera */
+--
+2.43.0
+
diff --git a/nix/pkgs/libcamera/0009-libcamera-software_isp-Add-Debayer-base-class.patch b/nix/pkgs/libcamera/0009-libcamera-software_isp-Add-Debayer-base-class.patch
new file mode 100644
index 0000000..f43b336
--- /dev/null
+++ b/nix/pkgs/libcamera/0009-libcamera-software_isp-Add-Debayer-base-class.patch
@@ -0,0 +1,272 @@
+From 8fc77447c0d76b0b52b19d23674049181c6cf8d2 Mon Sep 17 00:00:00 2001
+From: Hans de Goede <hdegoede@redhat.com>
+Date: Mon, 11 Dec 2023 14:46:53 +0100
+Subject: [PATCH 09/25] libcamera: software_isp: Add Debayer base class
+
+Add a base class for debayer implementations. This is intended to be
+suitable for both GPU (or otherwise) accelerated debayer implementations
+as well as CPU based debayering.
+
+Doxygen documentation by Dennis Bonke.
+
+Co-authored-by: Dennis Bonke <admin@dennisbonke.com>
+Signed-off-by: Dennis Bonke <admin@dennisbonke.com>
+Co-authored-by: Andrey Konovalov <andrey.konovalov@linaro.org>
+Signed-off-by: Andrey Konovalov <andrey.konovalov@linaro.org>
+Signed-off-by: Hans de Goede <hdegoede@redhat.com>
+Tested-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org> # sc8280xp Lenovo x13s
+Tested-by: Pavel Machek <pavel@ucw.cz>
+---
+ .../libcamera/internal/software_isp/debayer.h | 132 ++++++++++++++++++
+ .../internal/software_isp/debayer_params.h | 43 ++++++
+ .../internal/software_isp/meson.build | 2 +
+ src/libcamera/software_isp/debayer.cpp | 22 +++
+ src/libcamera/software_isp/meson.build | 1 +
+ 5 files changed, 200 insertions(+)
+ create mode 100644 include/libcamera/internal/software_isp/debayer.h
+ create mode 100644 include/libcamera/internal/software_isp/debayer_params.h
+ create mode 100644 src/libcamera/software_isp/debayer.cpp
+
+diff --git a/include/libcamera/internal/software_isp/debayer.h b/include/libcamera/internal/software_isp/debayer.h
+new file mode 100644
+index 00000000..39e6f393
+--- /dev/null
++++ b/include/libcamera/internal/software_isp/debayer.h
+@@ -0,0 +1,132 @@
++/* SPDX-License-Identifier: LGPL-2.1-or-later */
++/*
++ * Copyright (C) 2023, Linaro Ltd
++ * Copyright (C) 2023, Red Hat Inc.
++ *
++ * Authors:
++ * Hans de Goede <hdegoede@redhat.com>
++ *
++ * debayer.h - debayering base class
++ */
++
++#pragma once
++
++#include <stdint.h>
++
++#include <libcamera/base/log.h>
++#include <libcamera/base/signal.h>
++
++#include <libcamera/geometry.h>
++#include <libcamera/stream.h>
++
++#include "libcamera/internal/software_isp/debayer_params.h"
++
++namespace libcamera {
++
++class FrameBuffer;
++
++LOG_DECLARE_CATEGORY(Debayer)
++
++/**
++ * \class Debayer
++ * \brief Base debayering class
++ *
++ * Base class that provides functions for setting up the debayering process.
++ */
++class Debayer
++{
++public:
++ virtual ~Debayer() = 0;
++
++ /**
++ * \brief Configure the debayer object according to the passed in parameters.
++ * \param[in] inputCfg The input configuration.
++ * \param[in] outputCfgs The output configurations.
++ *
++ * \return 0 on success, a negative errno on failure.
++ */
++ virtual int configure(const StreamConfiguration &inputCfg,
++ const std::vector<std::reference_wrapper<StreamConfiguration>> &outputCfgs) = 0;
++
++ /**
++ * \brief Get the width and height at which the bayer pattern repeats.
++ * \param[in] inputFormat The input format.
++ *
++ * \return pattern size or an empty size for unsupported inputFormats.
++ */
++ virtual Size patternSize(PixelFormat inputFormat) = 0;
++
++ /**
++ * \brief Get the supported output formats.
++ * \param[in] inputFormat The input format.
++ *
++ * \return all supported output formats or an empty vector if there are none.
++ */
++ virtual std::vector<PixelFormat> formats(PixelFormat inputFormat) = 0;
++
++ /**
++ * \brief Get the stride and the frame size.
++ * \param[in] outputFormat The output format.
++ * \param[in] size The output size.
++ *
++ * \return a tuple of the stride and the frame size, or a tuple with 0,0 if there is no valid output config.
++ */
++ virtual std::tuple<unsigned int, unsigned int>
++ strideAndFrameSize(const PixelFormat &outputFormat, const Size &size) = 0;
++
++ /**
++ * \brief Process the bayer data into the requested format.
++ * \param[in] input The input buffer.
++ * \param[in] output The output buffer.
++ * \param[in] params The parameters to be used in debayering.
++ *
++ * \note DebayerParams is passed by value deliberately so that a copy is passed
++ * when this is run in another thread by invokeMethod().
++ */
++ virtual void process(FrameBuffer *input, FrameBuffer *output, DebayerParams params) = 0;
++
++ /**
++ * \brief Get the supported output sizes for the given input format and size.
++ * \param[in] inputFormat The input format.
++ * \param[in] inputSize The input size.
++ *
++ * \return The valid size ranges or an empty range if there are none.
++ */
++ SizeRange sizes(PixelFormat inputFormat, const Size &inputSize)
++ {
++ Size pattern_size = patternSize(inputFormat);
++
++ if (pattern_size.isNull())
++ return {};
++
++ /*
++ * For debayer interpolation a border of pattern-height x pattern-width
++ * is kept around the entire image. Combined with a minimum-size of
++ * pattern-height x pattern-width this means the input-size needs to be
++ * at least (3 * pattern-height) x (3 * pattern-width).
++ */
++ if (inputSize.width < (3 * pattern_size.width) ||
++ inputSize.height < (3 * pattern_size.height)) {
++ LOG(Debayer, Warning)
++ << "Input format size too small: " << inputSize.toString();
++ return {};
++ }
++
++ return SizeRange(Size(pattern_size.width, pattern_size.height),
++ Size((inputSize.width - 2 * pattern_size.width) & ~(pattern_size.width - 1),
++ (inputSize.height - 2 * pattern_size.height) & ~(pattern_size.height - 1)),
++ pattern_size.width, pattern_size.height);
++ }
++
++ /**
++ * \brief Signals when the input buffer is ready.
++ */
++ Signal<FrameBuffer *> inputBufferReady;
++
++ /**
++ * \brief Signals when the output buffer is ready.
++ */
++ Signal<FrameBuffer *> outputBufferReady;
++};
++
++} /* namespace libcamera */
+diff --git a/include/libcamera/internal/software_isp/debayer_params.h b/include/libcamera/internal/software_isp/debayer_params.h
+new file mode 100644
+index 00000000..8f515304
+--- /dev/null
++++ b/include/libcamera/internal/software_isp/debayer_params.h
+@@ -0,0 +1,43 @@
++/* SPDX-License-Identifier: LGPL-2.1-or-later */
++/*
++ * Copyright (C) 2023, Red Hat Inc.
++ *
++ * Authors:
++ * Hans de Goede <hdegoede@redhat.com>
++ *
++ * swstats.h - software statistics base class
++ */
++
++#pragma once
++
++namespace libcamera {
++
++/**
++ * \brief Struct to hold the debayer parameters.
++ */
++struct DebayerParams {
++ /**
++ * \brief Red Gain.
++ *
++ * 128 = 0.5, 256 = 1.0, 512 = 2.0, etc.
++ */
++ unsigned int gainR;
++ /**
++ * \brief Green Gain.
++ *
++ * 128 = 0.5, 256 = 1.0, 512 = 2.0, etc.
++ */
++ unsigned int gainG;
++ /**
++ * \brief Blue Gain.
++ *
++ * 128 = 0.5, 256 = 1.0, 512 = 2.0, etc.
++ */
++ unsigned int gainB;
++ /**
++ * \brief Gamma correction, 1.0 is no correction.
++ */
++ float gamma;
++};
++
++} /* namespace libcamera */
+diff --git a/include/libcamera/internal/software_isp/meson.build b/include/libcamera/internal/software_isp/meson.build
+index 1d9e4018..7e40925e 100644
+--- a/include/libcamera/internal/software_isp/meson.build
++++ b/include/libcamera/internal/software_isp/meson.build
+@@ -1,6 +1,8 @@
+ # SPDX-License-Identifier: CC0-1.0
+
+ libcamera_internal_headers += files([
++ 'debayer.h',
++ 'debayer_params.h',
+ 'swisp_stats.h',
+ 'swstats.h',
+ 'swstats_cpu.h',
+diff --git a/src/libcamera/software_isp/debayer.cpp b/src/libcamera/software_isp/debayer.cpp
+new file mode 100644
+index 00000000..442da1ac
+--- /dev/null
++++ b/src/libcamera/software_isp/debayer.cpp
+@@ -0,0 +1,22 @@
++/* SPDX-License-Identifier: LGPL-2.1-or-later */
++/*
++ * Copyright (C) 2023, Linaro Ltd
++ * Copyright (C) 2023, Red Hat Inc.
++ *
++ * Authors:
++ * Hans de Goede <hdegoede@redhat.com>
++ *
++ * debayer.cpp - debayer base class
++ */
++
++#include "libcamera/internal/software_isp/debayer.h"
++
++namespace libcamera {
++
++LOG_DEFINE_CATEGORY(Debayer)
++
++Debayer::~Debayer()
++{
++}
++
++} /* namespace libcamera */
+diff --git a/src/libcamera/software_isp/meson.build b/src/libcamera/software_isp/meson.build
+index d31c6217..d4ae5ac7 100644
+--- a/src/libcamera/software_isp/meson.build
++++ b/src/libcamera/software_isp/meson.build
+@@ -1,6 +1,7 @@
+ # SPDX-License-Identifier: CC0-1.0
+
+ libcamera_sources += files([
++ 'debayer.cpp',
+ 'swstats.cpp',
+ 'swstats_cpu.cpp',
+ ])
+--
+2.43.0
+
diff --git a/nix/pkgs/libcamera/0010-libcamera-software_isp-Add-DebayerCpu-class.patch b/nix/pkgs/libcamera/0010-libcamera-software_isp-Add-DebayerCpu-class.patch
new file mode 100644
index 0000000..56bc873
--- /dev/null
+++ b/nix/pkgs/libcamera/0010-libcamera-software_isp-Add-DebayerCpu-class.patch
@@ -0,0 +1,727 @@
+From 7eb7164ed7d90ea4cf9ec7e4f35fa8efa25f35e9 Mon Sep 17 00:00:00 2001
+From: Hans de Goede <hdegoede@redhat.com>
+Date: Mon, 11 Dec 2023 17:00:17 +0100
+Subject: [PATCH 10/25] libcamera: software_isp: Add DebayerCpu class
+
+Add CPU based debayering implementation. This initial implementation
+only supports debayering packed 10 bits per pixel bayer data in
+the 4 standard bayer orders.
+
+Doxygen documentation by Dennis Bonke.
+
+Co-authored-by: Dennis Bonke <admin@dennisbonke.com>
+Signed-off-by: Dennis Bonke <admin@dennisbonke.com>
+Co-authored-by: Andrey Konovalov <andrey.konovalov@linaro.org>
+Signed-off-by: Andrey Konovalov <andrey.konovalov@linaro.org>
+Co-authored-by: Pavel Machek <pavel@ucw.cz>
+Signed-off-by: Pavel Machek <pavel@ucw.cz>
+Signed-off-by: Hans de Goede <hdegoede@redhat.com>
+Tested-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org> # sc8280xp Lenovo x13s
+Tested-by: Pavel Machek <pavel@ucw.cz>
+---
+ .../internal/software_isp/debayer_cpu.h | 131 +++++
+ .../internal/software_isp/meson.build | 1 +
+ src/libcamera/software_isp/debayer_cpu.cpp | 528 ++++++++++++++++++
+ src/libcamera/software_isp/meson.build | 1 +
+ 4 files changed, 661 insertions(+)
+ create mode 100644 include/libcamera/internal/software_isp/debayer_cpu.h
+ create mode 100644 src/libcamera/software_isp/debayer_cpu.cpp
+
+diff --git a/include/libcamera/internal/software_isp/debayer_cpu.h b/include/libcamera/internal/software_isp/debayer_cpu.h
+new file mode 100644
+index 00000000..78573f44
+--- /dev/null
++++ b/include/libcamera/internal/software_isp/debayer_cpu.h
+@@ -0,0 +1,131 @@
++/* SPDX-License-Identifier: LGPL-2.1-or-later */
++/*
++ * Copyright (C) 2023, Linaro Ltd
++ * Copyright (C) 2023, Red Hat Inc.
++ *
++ * Authors:
++ * Hans de Goede <hdegoede@redhat.com>
++ *
++ * debayer_cpu.h - CPU based debayering header
++ */
++
++#pragma once
++
++#include <memory>
++#include <stdint.h>
++#include <vector>
++
++#include <libcamera/base/object.h>
++
++#include "libcamera/internal/software_isp/swstats_cpu.h"
++#include "libcamera/internal/software_isp/debayer.h"
++
++namespace libcamera {
++
++/**
++ * \class DebayerCpu
++ * \brief Class for debayering on the CPU
++ *
++ * Implementation for CPU based debayering
++ */
++class DebayerCpu : public Debayer, public Object
++{
++public:
++ /*
++ * FIXME this should be a plain (implementation independent) SwStats
++ * this can be fixed once getStats() is dropped.
++ */
++ /**
++ * \brief Constructs a DebayerCpu object.
++ * \param[in] stats Pointer to the stats object to use.
++ */
++ DebayerCpu(std::unique_ptr<SwStatsCpu> stats);
++ ~DebayerCpu();
++
++ /*
++ * Setup the Debayer object according to the passed in parameters.
++ * Return 0 on success, a negative errno value on failure
++ * (unsupported parameters).
++ */
++ int configure(const StreamConfiguration &inputCfg,
++ const std::vector<std::reference_wrapper<StreamConfiguration>> &outputCfgs);
++
++ /*
++ * Get width and height at which the bayer-pattern repeats.
++ * Return pattern-size or an empty Size for an unsupported inputFormat.
++ */
++ Size patternSize(PixelFormat inputFormat);
++
++ std::vector<PixelFormat> formats(PixelFormat input);
++ std::tuple<unsigned int, unsigned int>
++ strideAndFrameSize(const PixelFormat &outputFormat, const Size &size);
++
++ void process(FrameBuffer *input, FrameBuffer *output, DebayerParams params);
++
++ /**
++ * \brief Get the file descriptor for the statistics.
++ *
++ * \return the file descriptor pointing to the statistics.
++ */
++ const SharedFD &getStatsFD() { return stats_->getStatsFD(); }
++
++ /**
++ * \brief Get the output frame size.
++ *
++ * \return The output frame size.
++ */
++ unsigned int frameSize() { return outputConfig_.frameSize; }
++private:
++ void initLinePointers(const uint8_t *linePointers[], const uint8_t *src);
++ void shiftLinePointers(const uint8_t *linePointers[], const uint8_t *src);
++ void process2(const uint8_t *src, uint8_t *dst);
++ void process4(const uint8_t *src, uint8_t *dst);
++ /* CSI-2 packed 10-bit raw bayer format (all the 4 orders) */
++ void debayer10P_BGBG_BGR888(uint8_t *dst, const uint8_t *src[]);
++ void debayer10P_GRGR_BGR888(uint8_t *dst, const uint8_t *src[]);
++ void debayer10P_GBGB_BGR888(uint8_t *dst, const uint8_t *src[]);
++ void debayer10P_RGRG_BGR888(uint8_t *dst, const uint8_t *src[]);
++
++ typedef void (DebayerCpu::*debayerFn)(uint8_t *dst, const uint8_t *src[]);
++
++ struct DebayerInputConfig {
++ Size patternSize;
++ unsigned int bpp; /* Memory used per pixel, not precision */
++ unsigned int stride;
++ std::vector<PixelFormat> outputFormats;
++ };
++
++ struct DebayerOutputConfig {
++ unsigned int bpp; /* Memory used per pixel, not precision */
++ unsigned int stride;
++ unsigned int frameSize;
++ };
++
++ int getInputConfig(PixelFormat inputFormat, DebayerInputConfig &config);
++ int getOutputConfig(PixelFormat outputFormat, DebayerOutputConfig &config);
++ int setDebayerFunctions(PixelFormat inputFormat, PixelFormat outputFormat);
++
++ uint8_t gamma_[1024];
++ uint8_t red_[256];
++ uint8_t green_[256];
++ uint8_t blue_[256];
++ debayerFn debayer0_;
++ debayerFn debayer1_;
++ debayerFn debayer2_;
++ debayerFn debayer3_;
++ Rectangle window_;
++ DebayerInputConfig inputConfig_;
++ DebayerOutputConfig outputConfig_;
++ std::unique_ptr<SwStatsCpu> stats_;
++ uint8_t *lineBuffers_[5];
++ unsigned int lineBufferIndex_;
++ bool enableInputMemcpy_;
++ float gamma_correction_;
++ int measuredFrames_;
++ int64_t frameProcessTime_;
++ /* Skip 30 frames for things to stabilize then measure 30 frames */
++ static const int framesToSkip = 30;
++ static const int framesToMeasure = 60;
++};
++
++} /* namespace libcamera */
+diff --git a/include/libcamera/internal/software_isp/meson.build b/include/libcamera/internal/software_isp/meson.build
+index 7e40925e..b5a0d737 100644
+--- a/include/libcamera/internal/software_isp/meson.build
++++ b/include/libcamera/internal/software_isp/meson.build
+@@ -2,6 +2,7 @@
+
+ libcamera_internal_headers += files([
+ 'debayer.h',
++ 'debayer_cpu.h',
+ 'debayer_params.h',
+ 'swisp_stats.h',
+ 'swstats.h',
+diff --git a/src/libcamera/software_isp/debayer_cpu.cpp b/src/libcamera/software_isp/debayer_cpu.cpp
+new file mode 100644
+index 00000000..e0c3c658
+--- /dev/null
++++ b/src/libcamera/software_isp/debayer_cpu.cpp
+@@ -0,0 +1,528 @@
++/* SPDX-License-Identifier: LGPL-2.1-or-later */
++/*
++ * Copyright (C) 2023, Linaro Ltd
++ * Copyright (C) 2023, Red Hat Inc.
++ *
++ * Authors:
++ * Hans de Goede <hdegoede@redhat.com>
++ *
++ * debayer_cpu.cpp - CPU based debayering class
++ */
++
++#include "libcamera/internal/software_isp/debayer_cpu.h"
++
++#include <math.h>
++#include <stdlib.h>
++#include <time.h>
++
++#include <libcamera/formats.h>
++
++#include "libcamera/internal/bayer_format.h"
++#include "libcamera/internal/framebuffer.h"
++#include "libcamera/internal/mapped_framebuffer.h"
++
++namespace libcamera {
++
++DebayerCpu::DebayerCpu(std::unique_ptr<SwStatsCpu> stats)
++ : stats_(std::move(stats)), gamma_correction_(1.0)
++{
++#ifdef __x86_64__
++ enableInputMemcpy_ = false;
++#else
++ enableInputMemcpy_ = true;
++#endif
++ /* Initialize gamma to 1.0 curve */
++ for (int i = 0; i < 1024; i++)
++ gamma_[i] = i / 4;
++
++ for (int i = 0; i < 5; i++)
++ lineBuffers_[i] = NULL;
++}
++
++DebayerCpu::~DebayerCpu()
++{
++ for (int i = 0; i < 5; i++)
++ free(lineBuffers_[i]);
++}
++
++// RGR
++// GBG
++// RGR
++#define BGGR_BGR888(p, n, div) \
++ *dst++ = blue_[curr[x] / (div)]; \
++ *dst++ = green_[(prev[x] + curr[x - p] + curr[x + n] + next[x]) / (4 * (div))]; \
++ *dst++ = red_[(prev[x - p] + prev[x + n] + next[x - p] + next[x + n]) / (4 * (div))]; \
++ x++;
++
++// GBG
++// RGR
++// GBG
++#define GRBG_BGR888(p, n, div) \
++ *dst++ = blue_[(prev[x] + next[x]) / (2 * (div))]; \
++ *dst++ = green_[curr[x] / (div)]; \
++ *dst++ = red_[(curr[x - p] + curr[x + n]) / (2 * (div))]; \
++ x++;
++
++// GRG
++// BGB
++// GRG
++#define GBRG_BGR888(p, n, div) \
++ *dst++ = blue_[(curr[x - p] + curr[x + n]) / (2 * (div))]; \
++ *dst++ = green_[curr[x] / (div)]; \
++ *dst++ = red_[(prev[x] + next[x]) / (2 * (div))]; \
++ x++;
++
++// BGB
++// GRG
++// BGB
++#define RGGB_BGR888(p, n, div) \
++ *dst++ = blue_[(prev[x - p] + prev[x + n] + next[x - p] + next[x + n]) / (4 * (div))]; \
++ *dst++ = green_[(prev[x] + curr[x - p] + curr[x + n] + next[x]) / (4 * (div))]; \
++ *dst++ = red_[curr[x] / (div)]; \
++ x++;
++
++void DebayerCpu::debayer10P_BGBG_BGR888(uint8_t *dst, const uint8_t *src[])
++{
++ const int width_in_bytes = window_.width * 5 / 4;
++ const uint8_t *prev = (const uint8_t *)src[0];
++ const uint8_t *curr = (const uint8_t *)src[1];
++ const uint8_t *next = (const uint8_t *)src[2];
++
++ /*
++ * For the first pixel getting a pixel from the previous column uses
++ * x - 2 to skip the 5th byte with least-significant bits for 4 pixels.
++ * Same for last pixel (uses x + 2) and looking at the next column.
++ * x++ in the for-loop skips the 5th byte with 4 x 2 lsb-s for 10bit packed.
++ */
++ for (int x = 0; x < width_in_bytes; x++) {
++ /* Even pixel */
++ BGGR_BGR888(2, 1, 1)
++ /* Odd pixel BGGR -> GBRG */
++ GBRG_BGR888(1, 1, 1)
++ /* Same thing for next 2 pixels */
++ BGGR_BGR888(1, 1, 1)
++ GBRG_BGR888(1, 2, 1)
++ }
++}
++
++void DebayerCpu::debayer10P_GRGR_BGR888(uint8_t *dst, const uint8_t *src[])
++{
++ const int width_in_bytes = window_.width * 5 / 4;
++ const uint8_t *prev = (const uint8_t *)src[0];
++ const uint8_t *curr = (const uint8_t *)src[1];
++ const uint8_t *next = (const uint8_t *)src[2];
++
++ for (int x = 0; x < width_in_bytes; x++) {
++ /* Even pixel */
++ GRBG_BGR888(2, 1, 1)
++ /* Odd pixel GRBG -> RGGB */
++ RGGB_BGR888(1, 1, 1)
++ /* Same thing for next 2 pixels */
++ GRBG_BGR888(1, 1, 1)
++ RGGB_BGR888(1, 2, 1)
++ }
++}
++
++void DebayerCpu::debayer10P_GBGB_BGR888(uint8_t *dst, const uint8_t *src[])
++{
++ const int width_in_bytes = window_.width * 5 / 4;
++ const uint8_t *prev = (const uint8_t *)src[0];
++ const uint8_t *curr = (const uint8_t *)src[1];
++ const uint8_t *next = (const uint8_t *)src[2];
++
++ for (int x = 0; x < width_in_bytes; x++) {
++ /* Even pixel */
++ GBRG_BGR888(2, 1, 1)
++ /* Odd pixel GBGR -> BGGR */
++ BGGR_BGR888(1, 1, 1)
++ /* Same thing for next 2 pixels */
++ GBRG_BGR888(1, 1, 1)
++ BGGR_BGR888(1, 2, 1)
++ }
++}
++
++void DebayerCpu::debayer10P_RGRG_BGR888(uint8_t *dst, const uint8_t *src[])
++{
++ const int width_in_bytes = window_.width * 5 / 4;
++ const uint8_t *prev = (const uint8_t *)src[0];
++ const uint8_t *curr = (const uint8_t *)src[1];
++ const uint8_t *next = (const uint8_t *)src[2];
++
++ for (int x = 0; x < width_in_bytes; x++) {
++ /* Even pixel */
++ RGGB_BGR888(2, 1, 1)
++ /* Odd pixel RGGB -> GRBG*/
++ GRBG_BGR888(1, 1, 1)
++ /* Same thing for next 2 pixels */
++ RGGB_BGR888(1, 1, 1)
++ GRBG_BGR888(1, 2, 1)
++ }
++}
++
++static bool isStandardBayerOrder(BayerFormat::Order order)
++{
++ return order == BayerFormat::BGGR || order == BayerFormat::GBRG ||
++ order == BayerFormat::GRBG || order == BayerFormat::RGGB;
++}
++
++int DebayerCpu::getInputConfig(PixelFormat inputFormat, DebayerInputConfig &config)
++{
++ BayerFormat bayerFormat =
++ BayerFormat::fromPixelFormat(inputFormat);
++
++ if (bayerFormat.bitDepth == 10 &&
++ bayerFormat.packing == BayerFormat::Packing::CSI2 &&
++ isStandardBayerOrder(bayerFormat.order)) {
++ config.bpp = 10;
++ config.patternSize.width = 4; /* 5 bytes per *4* pixels */
++ config.patternSize.height = 2;
++ config.outputFormats = std::vector<PixelFormat>({ formats::RGB888 });
++ return 0;
++ }
++
++ LOG(Debayer, Info)
++ << "Unsupported input format " << inputFormat.toString();
++ return -EINVAL;
++}
++
++int DebayerCpu::getOutputConfig(PixelFormat outputFormat, DebayerOutputConfig &config)
++{
++ if (outputFormat == formats::RGB888) {
++ config.bpp = 24;
++ return 0;
++ }
++
++ LOG(Debayer, Info)
++ << "Unsupported output format " << outputFormat.toString();
++ return -EINVAL;
++}
++
++/* TODO: this ignores outputFormat since there is only 1 supported outputFormat for now */
++int DebayerCpu::setDebayerFunctions(PixelFormat inputFormat, [[maybe_unused]] PixelFormat outputFormat)
++{
++ BayerFormat bayerFormat =
++ BayerFormat::fromPixelFormat(inputFormat);
++
++ if (bayerFormat.bitDepth == 10 &&
++ bayerFormat.packing == BayerFormat::Packing::CSI2) {
++ switch (bayerFormat.order) {
++ case BayerFormat::BGGR:
++ debayer0_ = &DebayerCpu::debayer10P_BGBG_BGR888;
++ debayer1_ = &DebayerCpu::debayer10P_GRGR_BGR888;
++ return 0;
++ case BayerFormat::GBRG:
++ debayer0_ = &DebayerCpu::debayer10P_GBGB_BGR888;
++ debayer1_ = &DebayerCpu::debayer10P_RGRG_BGR888;
++ return 0;
++ case BayerFormat::GRBG:
++ debayer0_ = &DebayerCpu::debayer10P_GRGR_BGR888;
++ debayer1_ = &DebayerCpu::debayer10P_BGBG_BGR888;
++ return 0;
++ case BayerFormat::RGGB:
++ debayer0_ = &DebayerCpu::debayer10P_RGRG_BGR888;
++ debayer1_ = &DebayerCpu::debayer10P_GBGB_BGR888;
++ return 0;
++ default:
++ break;
++ }
++ }
++
++ LOG(Debayer, Error) << "Unsupported input output format combination";
++ return -EINVAL;
++}
++
++int DebayerCpu::configure(const StreamConfiguration &inputCfg,
++ const std::vector<std::reference_wrapper<StreamConfiguration>> &outputCfgs)
++{
++ if (getInputConfig(inputCfg.pixelFormat, inputConfig_) != 0)
++ return -EINVAL;
++
++ if (stats_->configure(inputCfg) != 0)
++ return -EINVAL;
++
++ const Size &stats_pattern_size = stats_->patternSize();
++ if (inputConfig_.patternSize.width != stats_pattern_size.width ||
++ inputConfig_.patternSize.height != stats_pattern_size.height) {
++ LOG(Debayer, Error)
++ << "mismatching stats and debayer pattern sizes for "
++ << inputCfg.pixelFormat.toString();
++ return -EINVAL;
++ }
++
++ inputConfig_.stride = inputCfg.stride;
++
++ if (outputCfgs.size() != 1) {
++ LOG(Debayer, Error)
++ << "Unsupported number of output streams: "
++ << outputCfgs.size();
++ return -EINVAL;
++ }
++
++ const StreamConfiguration &outputCfg = outputCfgs[0];
++ SizeRange outSizeRange = sizes(inputCfg.pixelFormat, inputCfg.size);
++ std::tie(outputConfig_.stride, outputConfig_.frameSize) =
++ strideAndFrameSize(outputCfg.pixelFormat, outputCfg.size);
++
++ if (!outSizeRange.contains(outputCfg.size) || outputConfig_.stride != outputCfg.stride) {
++ LOG(Debayer, Error)
++ << "Invalid output size/stride: "
++ << "\n " << outputCfg.size << " (" << outSizeRange << ")"
++ << "\n " << outputCfg.stride << " (" << outputConfig_.stride << ")";
++ return -EINVAL;
++ }
++
++ if (setDebayerFunctions(inputCfg.pixelFormat, outputCfg.pixelFormat) != 0)
++ return -EINVAL;
++
++ window_.x = ((inputCfg.size.width - outputCfg.size.width) / 2) &
++ ~(inputConfig_.patternSize.width - 1);
++ window_.y = ((inputCfg.size.height - outputCfg.size.height) / 2) &
++ ~(inputConfig_.patternSize.height - 1);
++ window_.width = outputCfg.size.width;
++ window_.height = outputCfg.size.height;
++
++ /* Don't pass x,y since process() already adjusts src before passing it */
++ stats_->setWindow(Rectangle(window_.size()));
++
++ for (unsigned int i = 0;
++ i < (inputConfig_.patternSize.height + 1) && enableInputMemcpy_;
++ i++) {
++ /* pad with patternSize.Width on both left and right side */
++ size_t lineLength = (window_.width + 2 * inputConfig_.patternSize.width) *
++ inputConfig_.bpp / 8;
++
++ free(lineBuffers_[i]);
++ lineBuffers_[i] = (uint8_t *)malloc(lineLength);
++ if (!lineBuffers_[i])
++ return -ENOMEM;
++ }
++
++ measuredFrames_ = 0;
++ frameProcessTime_ = 0;
++
++ return 0;
++}
++
++Size DebayerCpu::patternSize(PixelFormat inputFormat)
++{
++ DebayerCpu::DebayerInputConfig config;
++
++ if (getInputConfig(inputFormat, config) != 0)
++ return {};
++
++ return config.patternSize;
++}
++
++std::vector<PixelFormat> DebayerCpu::formats(PixelFormat inputFormat)
++{
++ DebayerCpu::DebayerInputConfig config;
++
++ if (getInputConfig(inputFormat, config) != 0)
++ return std::vector<PixelFormat>();
++
++ return config.outputFormats;
++}
++
++std::tuple<unsigned int, unsigned int>
++DebayerCpu::strideAndFrameSize(const PixelFormat &outputFormat, const Size &size)
++{
++ DebayerCpu::DebayerOutputConfig config;
++
++ if (getOutputConfig(outputFormat, config) != 0)
++ return std::make_tuple(0, 0);
++
++ /* round up to multiple of 8 for 64 bits alignment */
++ unsigned int stride = (size.width * config.bpp / 8 + 7) & ~7;
++
++ return std::make_tuple(stride, stride * size.height);
++}
++
++void DebayerCpu::initLinePointers(const uint8_t *linePointers[], const uint8_t *src)
++{
++ const int patternHeight = inputConfig_.patternSize.height;
++
++ for (int i = 0; i < patternHeight; i++)
++ linePointers[i + 1] = src +
++ (-patternHeight / 2 + i) * (int)inputConfig_.stride;
++
++ if (!enableInputMemcpy_)
++ return;
++
++ for (int i = 0; i < patternHeight; i++) {
++ /* pad with patternSize.Width on both left and right side */
++ size_t lineLength = (window_.width + 2 * inputConfig_.patternSize.width) *
++ inputConfig_.bpp / 8;
++ int padding = inputConfig_.patternSize.width * inputConfig_.bpp / 8;
++
++ memcpy(lineBuffers_[i], linePointers[i + 1] - padding, lineLength);
++ linePointers[i + 1] = lineBuffers_[i] + padding;
++ }
++
++ /* Point lineBufferIndex_ to first unused lineBuffer */
++ lineBufferIndex_ = patternHeight;
++}
++
++void DebayerCpu::shiftLinePointers(const uint8_t *linePointers[], const uint8_t *src)
++{
++ const int patternHeight = inputConfig_.patternSize.height;
++
++ for (int i = 0; i < patternHeight; i++)
++ linePointers[i] = linePointers[i + 1];
++
++ linePointers[patternHeight] = src +
++ (patternHeight / 2) * (int)inputConfig_.stride;
++
++ if (!enableInputMemcpy_)
++ return;
++
++ size_t lineLength = (window_.width + 2 * inputConfig_.patternSize.width) *
++ inputConfig_.bpp / 8;
++ int padding = inputConfig_.patternSize.width * inputConfig_.bpp / 8;
++ memcpy(lineBuffers_[lineBufferIndex_], linePointers[patternHeight] - padding, lineLength);
++ linePointers[patternHeight] = lineBuffers_[lineBufferIndex_] + padding;
++
++ lineBufferIndex_ = (lineBufferIndex_ + 1) % (patternHeight + 1);
++}
++
++void DebayerCpu::process2(const uint8_t *src, uint8_t *dst)
++{
++ const unsigned int y_end = window_.y + window_.height;
++ const uint8_t *linePointers[3];
++
++ /* Adjust src to top left corner of the window */
++ src += window_.y * inputConfig_.stride + window_.x * inputConfig_.bpp / 8;
++
++ initLinePointers(linePointers, src);
++
++ for (unsigned int y = window_.y; y < y_end; y += 2) {
++ shiftLinePointers(linePointers, src);
++ stats_->processLine0(y, linePointers);
++ (this->*debayer0_)(dst, linePointers);
++ src += inputConfig_.stride;
++ dst += outputConfig_.stride;
++
++ shiftLinePointers(linePointers, src);
++ (this->*debayer1_)(dst, linePointers);
++ src += inputConfig_.stride;
++ dst += outputConfig_.stride;
++ }
++}
++
++void DebayerCpu::process4(const uint8_t *src, uint8_t *dst)
++{
++ const unsigned int y_end = window_.y + window_.height;
++ const uint8_t *linePointers[5];
++
++ /* Adjust src to top left corner of the window */
++ src += window_.y * inputConfig_.stride + window_.x * inputConfig_.bpp / 8;
++
++ initLinePointers(linePointers, src);
++
++ for (unsigned int y = window_.y; y < y_end; y += 4) {
++ shiftLinePointers(linePointers, src);
++ stats_->processLine0(y, linePointers);
++ (this->*debayer0_)(dst, linePointers);
++ src += inputConfig_.stride;
++ dst += outputConfig_.stride;
++
++ shiftLinePointers(linePointers, src);
++ (this->*debayer1_)(dst, linePointers);
++ src += inputConfig_.stride;
++ dst += outputConfig_.stride;
++
++ shiftLinePointers(linePointers, src);
++ stats_->processLine2(y, linePointers);
++ (this->*debayer2_)(dst, linePointers);
++ src += inputConfig_.stride;
++ dst += outputConfig_.stride;
++
++ shiftLinePointers(linePointers, src);
++ (this->*debayer3_)(dst, linePointers);
++ src += inputConfig_.stride;
++ dst += outputConfig_.stride;
++ }
++}
++
++static inline int64_t timeDiff(timespec &after, timespec &before)
++{
++ return (after.tv_sec - before.tv_sec) * 1000000000LL +
++ (int64_t)after.tv_nsec - (int64_t)before.tv_nsec;
++}
++
++void DebayerCpu::process(FrameBuffer *input, FrameBuffer *output, DebayerParams params)
++{
++ timespec frameStartTime;
++
++ if (measuredFrames_ < DebayerCpu::framesToMeasure) {
++ frameStartTime = {};
++ clock_gettime(CLOCK_MONOTONIC_RAW, &frameStartTime);
++ }
++
++ /* Apply DebayerParams */
++ if (params.gamma != gamma_correction_) {
++ for (int i = 0; i < 1024; i++)
++ gamma_[i] = 255 * powf(i / 1023.0, params.gamma);
++
++ gamma_correction_ = params.gamma;
++ }
++
++ for (int i = 0; i < 256; i++) {
++ int idx;
++
++ /* Apply gamma after gain! */
++ idx = std::min({ i * params.gainR / 64U, 1023U });
++ red_[i] = gamma_[idx];
++
++ idx = std::min({ i * params.gainG / 64U, 1023U });
++ green_[i] = gamma_[idx];
++
++ idx = std::min({ i * params.gainB / 64U, 1023U });
++ blue_[i] = gamma_[idx];
++ }
++
++ /* Copy metadata from the input buffer */
++ FrameMetadata &metadata = output->_d()->metadata();
++ metadata.status = input->metadata().status;
++ metadata.sequence = input->metadata().sequence;
++ metadata.timestamp = input->metadata().timestamp;
++
++ MappedFrameBuffer in(input, MappedFrameBuffer::MapFlag::Read);
++ MappedFrameBuffer out(output, MappedFrameBuffer::MapFlag::Write);
++ if (!in.isValid() || !out.isValid()) {
++ LOG(Debayer, Error) << "mmap-ing buffer(s) failed";
++ metadata.status = FrameMetadata::FrameError;
++ return;
++ }
++
++ stats_->startFrame();
++
++ if (inputConfig_.patternSize.height == 2)
++ process2(in.planes()[0].data(), out.planes()[0].data());
++ else
++ process4(in.planes()[0].data(), out.planes()[0].data());
++
++ metadata.planes()[0].bytesused = out.planes()[0].size();
++
++ /* Measure before emitting signals */
++ if (measuredFrames_ < DebayerCpu::framesToMeasure &&
++ ++measuredFrames_ > DebayerCpu::framesToSkip) {
++ timespec frameEndTime = {};
++ clock_gettime(CLOCK_MONOTONIC_RAW, &frameEndTime);
++ frameProcessTime_ += timeDiff(frameEndTime, frameStartTime);
++ if (measuredFrames_ == DebayerCpu::framesToMeasure) {
++ const int measuredFrames = DebayerCpu::framesToMeasure -
++ DebayerCpu::framesToSkip;
++ LOG(Debayer, Info)
++ << "Processed " << measuredFrames
++ << " frames in " << frameProcessTime_ / 1000 << "us, "
++ << frameProcessTime_ / (1000 * measuredFrames)
++ << " us/frame";
++ }
++ }
++
++ stats_->finishFrame();
++ outputBufferReady.emit(output);
++ inputBufferReady.emit(input);
++}
++
++} /* namespace libcamera */
+diff --git a/src/libcamera/software_isp/meson.build b/src/libcamera/software_isp/meson.build
+index d4ae5ac7..6d7a44d7 100644
+--- a/src/libcamera/software_isp/meson.build
++++ b/src/libcamera/software_isp/meson.build
+@@ -2,6 +2,7 @@
+
+ libcamera_sources += files([
+ 'debayer.cpp',
++ 'debayer_cpu.cpp',
+ 'swstats.cpp',
+ 'swstats_cpu.cpp',
+ ])
+--
+2.43.0
+
diff --git a/nix/pkgs/libcamera/0011-libcamera-ipa-add-Soft-IPA-common-files.patch b/nix/pkgs/libcamera/0011-libcamera-ipa-add-Soft-IPA-common-files.patch
new file mode 100644
index 0000000..d5fd94f
--- /dev/null
+++ b/nix/pkgs/libcamera/0011-libcamera-ipa-add-Soft-IPA-common-files.patch
@@ -0,0 +1,256 @@
+From 05b353f1e45f2af0d0989261210b4bedef5144de Mon Sep 17 00:00:00 2001
+From: Andrey Konovalov <andrey.konovalov@linaro.org>
+Date: Mon, 11 Dec 2023 23:41:58 +0300
+Subject: [PATCH 11/25] libcamera: ipa: add Soft IPA common files
+
+Define the Soft IPA main and event interfaces, add IPASoftBase
+class the Soft IPA implementation inherit from.
+
+Signed-off-by: Andrey Konovalov <andrey.konovalov@linaro.org>
+Signed-off-by: Hans de Goede <hdegoede@redhat.com>
+Tested-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org> # sc8280xp Lenovo x13s
+Tested-by: Pavel Machek <pavel@ucw.cz>
+---
+ Documentation/Doxyfile.in | 1 +
+ include/libcamera/ipa/meson.build | 1 +
+ include/libcamera/ipa/soft.mojom | 29 ++++++++++++
+ src/ipa/simple/common/meson.build | 17 +++++++
+ src/ipa/simple/common/soft_base.cpp | 73 +++++++++++++++++++++++++++++
+ src/ipa/simple/common/soft_base.h | 50 ++++++++++++++++++++
+ src/ipa/simple/meson.build | 3 ++
+ 7 files changed, 174 insertions(+)
+ create mode 100644 include/libcamera/ipa/soft.mojom
+ create mode 100644 src/ipa/simple/common/meson.build
+ create mode 100644 src/ipa/simple/common/soft_base.cpp
+ create mode 100644 src/ipa/simple/common/soft_base.h
+ create mode 100644 src/ipa/simple/meson.build
+
+diff --git a/Documentation/Doxyfile.in b/Documentation/Doxyfile.in
+index a86ea6c1..2be8d47b 100644
+--- a/Documentation/Doxyfile.in
++++ b/Documentation/Doxyfile.in
+@@ -44,6 +44,7 @@ EXCLUDE = @TOP_SRCDIR@/include/libcamera/base/span.h \
+ @TOP_SRCDIR@/src/libcamera/pipeline/ \
+ @TOP_SRCDIR@/src/libcamera/tracepoints.cpp \
+ @TOP_BUILDDIR@/include/libcamera/internal/tracepoints.h \
++ @TOP_BUILDDIR@/include/libcamera/ipa/soft_ipa_interface.h \
+ @TOP_BUILDDIR@/src/libcamera/proxy/
+
+ EXCLUDE_PATTERNS = @TOP_BUILDDIR@/include/libcamera/ipa/*_serializer.h \
+diff --git a/include/libcamera/ipa/meson.build b/include/libcamera/ipa/meson.build
+index f3b4881c..894e38a6 100644
+--- a/include/libcamera/ipa/meson.build
++++ b/include/libcamera/ipa/meson.build
+@@ -65,6 +65,7 @@ pipeline_ipa_mojom_mapping = {
+ 'ipu3': 'ipu3.mojom',
+ 'rkisp1': 'rkisp1.mojom',
+ 'rpi/vc4': 'raspberrypi.mojom',
++ 'simple/simple': 'soft.mojom',
+ 'vimc': 'vimc.mojom',
+ }
+
+diff --git a/include/libcamera/ipa/soft.mojom b/include/libcamera/ipa/soft.mojom
+new file mode 100644
+index 00000000..2dae652b
+--- /dev/null
++++ b/include/libcamera/ipa/soft.mojom
+@@ -0,0 +1,29 @@
++/* SPDX-License-Identifier: LGPL-2.1-or-later */
++
++/*
++ * \todo Document the interface and remove the related EXCLUDE_PATTERNS entry.
++ * \todo Add a way to tell SoftIPA the list of params SoftISP accepts?
++ */
++
++module ipa.soft;
++
++import "include/libcamera/ipa/core.mojom";
++
++interface IPASoftInterface {
++ init(libcamera.IPASettings settings,
++ libcamera.SharedFD fdStats,
++ libcamera.SharedFD fdParams,
++ libcamera.ControlInfoMap sensorCtrlInfoMap)
++ => (int32 ret);
++ start() => (int32 ret);
++ stop();
++ configure(libcamera.ControlInfoMap sensorCtrlInfoMap)
++ => (int32 ret);
++
++ [async] processStats(libcamera.ControlList sensorControls);
++};
++
++interface IPASoftEventInterface {
++ setSensorControls(libcamera.ControlList sensorControls);
++ setIspParams(int32 dummy);
++};
+diff --git a/src/ipa/simple/common/meson.build b/src/ipa/simple/common/meson.build
+new file mode 100644
+index 00000000..023e617b
+--- /dev/null
++++ b/src/ipa/simple/common/meson.build
+@@ -0,0 +1,17 @@
++# SPDX-License-Identifier: CC0-1.0
++
++soft_ipa_common_sources = files([
++ 'soft_base.cpp',
++])
++
++soft_ipa_common_includes = [
++ include_directories('..'),
++]
++
++soft_ipa_common_deps = [
++ libcamera_private,
++]
++
++soft_ipa_common_lib = static_library('soft_ipa_common', soft_ipa_common_sources,
++ include_directories : soft_ipa_common_includes,
++ dependencies : soft_ipa_common_deps)
+diff --git a/src/ipa/simple/common/soft_base.cpp b/src/ipa/simple/common/soft_base.cpp
+new file mode 100644
+index 00000000..b4ed9023
+--- /dev/null
++++ b/src/ipa/simple/common/soft_base.cpp
+@@ -0,0 +1,73 @@
++/* SPDX-License-Identifier: LGPL-2.1-or-later */
++/*
++ * Copyright (C) 2023, Linaro Ltd
++ *
++ * soft-base.cpp - Software IPA base class
++ */
++
++#include "soft_base.h"
++
++#include <sys/mman.h>
++
++#include <libcamera/base/file.h>
++#include <libcamera/base/log.h>
++
++#include <libcamera/control_ids.h>
++
++namespace libcamera {
++
++LOG_DEFINE_CATEGORY(IPASoft)
++
++namespace ipa::soft {
++
++IPASoftBase::IPASoftBase()
++{
++}
++
++IPASoftBase::~IPASoftBase()
++{
++}
++
++int IPASoftBase::init([[maybe_unused]] const IPASettings &settings,
++ const SharedFD &fdStats,
++ const SharedFD &fdParams,
++ const ControlInfoMap &sensorInfoMap)
++{
++ fdStats_ = std::move(fdStats);
++ if (!fdStats_.isValid()) {
++ LOG(IPASoft, Error) << "Invalid Statistics handle";
++ return -ENODEV;
++ }
++
++ fdParams_ = std::move(fdParams);
++ if (!fdParams_.isValid()) {
++ LOG(IPASoft, Error) << "Invalid Parameters handle";
++ return -ENODEV;
++ }
++
++ return platformInit(sensorInfoMap);
++}
++
++int IPASoftBase::configure(const ControlInfoMap &sensorInfoMap)
++{
++ return platformConfigure(sensorInfoMap);
++}
++
++int IPASoftBase::start()
++{
++ return platformStart();
++}
++
++void IPASoftBase::stop()
++{
++ return platformStop();
++}
++
++void IPASoftBase::processStats(const ControlList &sensorControls)
++{
++ return platformProcessStats(sensorControls);
++}
++
++} /* namespace ipa::soft */
++
++} /* namespace libcamera */
+diff --git a/src/ipa/simple/common/soft_base.h b/src/ipa/simple/common/soft_base.h
+new file mode 100644
+index 00000000..85c98702
+--- /dev/null
++++ b/src/ipa/simple/common/soft_base.h
+@@ -0,0 +1,50 @@
++/* SPDX-License-Identifier: LGPL-2.1-or-later */
++/*
++ * Copyright (C) 2023, Linaro Ltd
++ *
++ * soft-base.h - Software IPA base class
++ */
++#pragma once
++
++#include <libcamera/base/shared_fd.h>
++
++#include <libcamera/controls.h>
++
++#include <libcamera/ipa/soft_ipa_interface.h>
++
++namespace libcamera {
++
++namespace ipa::soft {
++
++class IPASoftBase : public ipa::soft::IPASoftInterface
++{
++public:
++ IPASoftBase();
++ ~IPASoftBase();
++
++ int init(const IPASettings &settings,
++ const SharedFD &fdStats,
++ const SharedFD &fdParams,
++ const ControlInfoMap &sensorInfoMap) override;
++ int configure(const ControlInfoMap &sensorInfoMap) override;
++
++ int start() override;
++ void stop() override;
++
++ void processStats(const ControlList &sensorControls) override;
++
++protected:
++ SharedFD fdStats_;
++ SharedFD fdParams_;
++
++private:
++ virtual int platformInit(const ControlInfoMap &sensorInfoMap) = 0;
++ virtual int platformConfigure(const ControlInfoMap &sensorInfoMap) = 0;
++ virtual int platformStart() = 0;
++ virtual void platformStop() = 0;
++ virtual void platformProcessStats(const ControlList &sensorControls) = 0;
++};
++
++} /* namespace ipa::soft */
++
++} /* namespace libcamera */
+diff --git a/src/ipa/simple/meson.build b/src/ipa/simple/meson.build
+new file mode 100644
+index 00000000..9688bbdb
+--- /dev/null
++++ b/src/ipa/simple/meson.build
+@@ -0,0 +1,3 @@
++# SPDX-License-Identifier: CC0-1.0
++
++subdir('common')
+--
+2.43.0
+
diff --git a/nix/pkgs/libcamera/0012-libcamera-ipa-Soft-IPA-add-a-Simple-Soft-IPA-impleme.patch b/nix/pkgs/libcamera/0012-libcamera-ipa-Soft-IPA-add-a-Simple-Soft-IPA-impleme.patch
new file mode 100644
index 0000000..b7cb274
--- /dev/null
+++ b/nix/pkgs/libcamera/0012-libcamera-ipa-Soft-IPA-add-a-Simple-Soft-IPA-impleme.patch
@@ -0,0 +1,407 @@
+From c0886381a2bbe494b900d699a3858573316059b2 Mon Sep 17 00:00:00 2001
+From: Andrey Konovalov <andrey.konovalov@linaro.org>
+Date: Mon, 11 Dec 2023 23:47:47 +0300
+Subject: [PATCH 12/25] libcamera: ipa: Soft IPA: add a Simple Soft IPA
+ implementation
+
+Auto exposure/gain and AWB implementation by Dennis, Toon and Martti.
+
+Co-authored-by: Dennis Bonke <admin@dennisbonke.com>
+Signed-off-by: Dennis Bonke <admin@dennisbonke.com>
+Co-authored-by: Marttico <g.martti@gmail.com>
+Signed-off-by: Marttico <g.martti@gmail.com>
+Co-authored-by: Toon Langendam <t.langendam@gmail.com>
+Signed-off-by: Toon Langendam <t.langendam@gmail.com>
+Signed-off-by: Andrey Konovalov <andrey.konovalov@linaro.org>
+Signed-off-by: Hans de Goede <hdegoede@redhat.com>
+Tested-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org> # sc8280xp Lenovo x13s
+Tested-by: Pavel Machek <pavel@ucw.cz>
+---
+ meson_options.txt | 3 +-
+ src/ipa/simple/meson.build | 9 +
+ src/ipa/simple/simple/data/meson.build | 9 +
+ src/ipa/simple/simple/data/soft.conf | 3 +
+ src/ipa/simple/simple/meson.build | 26 +++
+ src/ipa/simple/simple/soft_simple.cpp | 273 +++++++++++++++++++++++++
+ 6 files changed, 322 insertions(+), 1 deletion(-)
+ create mode 100644 src/ipa/simple/simple/data/meson.build
+ create mode 100644 src/ipa/simple/simple/data/soft.conf
+ create mode 100644 src/ipa/simple/simple/meson.build
+ create mode 100644 src/ipa/simple/simple/soft_simple.cpp
+
+diff --git a/meson_options.txt b/meson_options.txt
+index 5fdc7be8..8ec08658 100644
+--- a/meson_options.txt
++++ b/meson_options.txt
+@@ -27,7 +27,7 @@ option('gstreamer',
+
+ option('ipas',
+ type : 'array',
+- choices : ['ipu3', 'rkisp1', 'rpi/vc4', 'vimc'],
++ choices : ['ipu3', 'rkisp1', 'rpi/vc4', 'simple/simple', 'vimc'],
+ description : 'Select which IPA modules to build')
+
+ option('lc-compliance',
+@@ -46,6 +46,7 @@ option('pipelines',
+ 'rkisp1',
+ 'rpi/vc4',
+ 'simple',
++ 'simple/simple',
+ 'uvcvideo',
+ 'vimc'
+ ],
+diff --git a/src/ipa/simple/meson.build b/src/ipa/simple/meson.build
+index 9688bbdb..14be5dc2 100644
+--- a/src/ipa/simple/meson.build
++++ b/src/ipa/simple/meson.build
+@@ -1,3 +1,12 @@
+ # SPDX-License-Identifier: CC0-1.0
+
+ subdir('common')
++
++foreach pipeline : pipelines
++ pipeline = pipeline.split('/')
++ if pipeline.length() < 2 or pipeline[0] != 'simple'
++ continue
++ endif
++
++ subdir(pipeline[1])
++endforeach
+diff --git a/src/ipa/simple/simple/data/meson.build b/src/ipa/simple/simple/data/meson.build
+new file mode 100644
+index 00000000..33548cc6
+--- /dev/null
++++ b/src/ipa/simple/simple/data/meson.build
+@@ -0,0 +1,9 @@
++# SPDX-License-Identifier: CC0-1.0
++
++conf_files = files([
++ 'soft.conf',
++])
++
++install_data(conf_files,
++ install_dir : ipa_data_dir / 'soft',
++ install_tag : 'runtime')
+diff --git a/src/ipa/simple/simple/data/soft.conf b/src/ipa/simple/simple/data/soft.conf
+new file mode 100644
+index 00000000..0c70e7c0
+--- /dev/null
++++ b/src/ipa/simple/simple/data/soft.conf
+@@ -0,0 +1,3 @@
++# SPDX-License-Identifier: LGPL-2.1-or-later
++#
++# Dummy configuration file for the soft IPA.
+diff --git a/src/ipa/simple/simple/meson.build b/src/ipa/simple/simple/meson.build
+new file mode 100644
+index 00000000..8b5d76b5
+--- /dev/null
++++ b/src/ipa/simple/simple/meson.build
+@@ -0,0 +1,26 @@
++# SPDX-License-Identifier: CC0-1.0
++
++ipa_name = 'ipa_soft_simple'
++
++mod = shared_module(ipa_name,
++ ['soft_simple.cpp', libcamera_generated_ipa_headers],
++ name_prefix : '',
++ include_directories : [ipa_includes, libipa_includes, '..'],
++ dependencies : libcamera_private,
++ link_with : libipa,
++ link_whole : soft_ipa_common_lib,
++ install : true,
++ install_dir : ipa_install_dir)
++
++if ipa_sign_module
++ custom_target(ipa_name + '.so.sign',
++ input : mod,
++ output : ipa_name + '.so.sign',
++ command : [ipa_sign, ipa_priv_key, '@INPUT@', '@OUTPUT@'],
++ install : false,
++ build_by_default : true)
++endif
++
++subdir('data')
++
++ipa_names += ipa_name
+diff --git a/src/ipa/simple/simple/soft_simple.cpp b/src/ipa/simple/simple/soft_simple.cpp
+new file mode 100644
+index 00000000..93fc1545
+--- /dev/null
++++ b/src/ipa/simple/simple/soft_simple.cpp
+@@ -0,0 +1,273 @@
++/* SPDX-License-Identifier: LGPL-2.1-or-later */
++/*
++ * Copyright (C) 2023, Linaro Ltd
++ *
++ * soft_simple.cpp - Simple Software Image Processing Algorithm module
++ */
++
++#include <sys/mman.h>
++
++#include <libcamera/base/file.h>
++#include <libcamera/base/log.h>
++
++#include <libcamera/control_ids.h>
++
++#include <libcamera/ipa/ipa_interface.h>
++#include <libcamera/ipa/ipa_module_info.h>
++
++#include "libcamera/internal/camera_sensor.h"
++#include "libcamera/internal/software_isp/debayer_params.h"
++#include "libcamera/internal/software_isp/swisp_stats.h"
++
++#include "common/soft_base.h"
++
++#define EXPOSURE_OPTIMAL_VALUE 2.5
++#define EXPOSURE_SATISFACTORY_OFFSET 0.2
++
++namespace libcamera {
++
++LOG_DECLARE_CATEGORY(IPASoft)
++
++namespace ipa::soft {
++
++class IPASoftSimple final : public IPASoftBase
++{
++public:
++ IPASoftSimple()
++ : IPASoftBase(), ignore_updates_(0)
++ {
++ }
++
++ ~IPASoftSimple()
++ {
++ if (stats_)
++ munmap(stats_, sizeof(SwIspStats));
++ if (params_)
++ munmap(params_, sizeof(DebayerParams));
++ }
++
++ int platformInit(const ControlInfoMap &sensorInfoMap) override;
++ int platformConfigure(const ControlInfoMap &sensorInfoMap) override;
++ int platformStart() override;
++ void platformStop() override;
++ void platformProcessStats(const ControlList &sensorControls) override;
++
++private:
++ void update_exposure(double exposuremsv);
++
++ DebayerParams *params_;
++ SwIspStats *stats_;
++ int exposure_min_, exposure_max_;
++ int again_min_, again_max_;
++ int again_, exposure_;
++ int ignore_updates_;
++};
++
++int IPASoftSimple::platformInit(const ControlInfoMap &sensorInfoMap)
++{
++ params_ = static_cast<DebayerParams *>(mmap(nullptr, sizeof(DebayerParams),
++ PROT_WRITE, MAP_SHARED,
++ fdParams_.get(), 0));
++ if (!params_) {
++ LOG(IPASoft, Error) << "Unable to map Parameters";
++ return -ENODEV;
++ }
++
++ stats_ = static_cast<SwIspStats *>(mmap(nullptr, sizeof(SwIspStats),
++ PROT_READ, MAP_SHARED,
++ fdStats_.get(), 0));
++ if (!stats_) {
++ LOG(IPASoft, Error) << "Unable to map Statistics";
++ return -ENODEV;
++ }
++
++ if (sensorInfoMap.find(V4L2_CID_EXPOSURE) == sensorInfoMap.end()) {
++ LOG(IPASoft, Error) << "Don't have exposure control";
++ return -EINVAL;
++ }
++
++ if (sensorInfoMap.find(V4L2_CID_ANALOGUE_GAIN) == sensorInfoMap.end()) {
++ LOG(IPASoft, Error) << "Don't have gain control";
++ return -EINVAL;
++ }
++
++ return 0;
++}
++
++int IPASoftSimple::platformConfigure(const ControlInfoMap &sensorInfoMap)
++{
++ const ControlInfo &exposure_info = sensorInfoMap.find(V4L2_CID_EXPOSURE)->second;
++ const ControlInfo &gain_info = sensorInfoMap.find(V4L2_CID_ANALOGUE_GAIN)->second;
++
++ exposure_min_ = exposure_info.min().get<int>();
++ if (!exposure_min_) {
++ LOG(IPASoft, Warning) << "Minimum exposure is zero, that can't be linear";
++ exposure_min_ = 1;
++ }
++ exposure_max_ = exposure_info.max().get<int>();
++ again_min_ = gain_info.min().get<int>();
++ if (!again_min_) {
++ LOG(IPASoft, Warning) << "Minimum gain is zero, that can't be linear";
++ again_min_ = 100;
++ }
++ again_max_ = gain_info.max().get<int>();
++
++ LOG(IPASoft, Info) << "Exposure " << exposure_min_ << "-" << exposure_max_
++ << ", gain " << again_min_ << "-" << again_max_;
++
++ return 0;
++}
++
++int IPASoftSimple::platformStart()
++{
++ return 0;
++}
++
++void IPASoftSimple::platformStop()
++{
++}
++
++void IPASoftSimple::platformProcessStats(const ControlList &sensorControls)
++{
++ /*
++ * Calculate red and blue gains for AWB.
++ * Clamp max gain at 4.0, this also avoids 0 division.
++ */
++ if (stats_->sumR_ <= stats_->sumG_ / 4)
++ params_->gainR = 1024;
++ else
++ params_->gainR = 256 * stats_->sumG_ / stats_->sumR_;
++
++ if (stats_->sumB_ <= stats_->sumG_ / 4)
++ params_->gainB = 1024;
++ else
++ params_->gainB = 256 * stats_->sumG_ / stats_->sumB_;
++
++ /* Green gain and gamma values are fixed */
++ params_->gainG = 256;
++ params_->gamma = 0.5;
++
++ setIspParams.emit(0);
++
++ /*
++ * AE / AGC, use 2 frames delay to make sure that the exposure and
++ * the gain set have applied to the camera sensor.
++ */
++ if (ignore_updates_ > 0) {
++ --ignore_updates_;
++ return;
++ }
++
++ unsigned int denom = 0;
++ unsigned int num = 0;
++ unsigned int y_histogramSmall[5] = {};
++
++ for (int i = 0; i < 16; i++)
++ y_histogramSmall[(i - i / 8) / 3] += stats_->y_histogram[i];
++
++ for (int i = 0; i < 5; i++) {
++ LOG(IPASoft, Debug) << i << ": " << y_histogramSmall[i];
++ denom += y_histogramSmall[i];
++ num += y_histogramSmall[i] * (i + 1);
++ }
++
++ float exposuremsv = (float)num / denom;
++
++ /* sanity check */
++ if (!sensorControls.contains(V4L2_CID_EXPOSURE) ||
++ !sensorControls.contains(V4L2_CID_ANALOGUE_GAIN)) {
++ LOG(IPASoft, Error) << "Control(s) missing";
++ return;
++ }
++
++ ControlList ctrls(sensorControls);
++
++ exposure_ = ctrls.get(V4L2_CID_EXPOSURE).get<int>();
++ again_ = ctrls.get(V4L2_CID_ANALOGUE_GAIN).get<int>();
++
++ update_exposure(exposuremsv);
++
++ ctrls.set(V4L2_CID_EXPOSURE, exposure_);
++ ctrls.set(V4L2_CID_ANALOGUE_GAIN, again_);
++
++ ignore_updates_ = 2;
++
++ setSensorControls.emit(ctrls);
++
++ LOG(IPASoft, Debug) << "exposuremsv " << exposuremsv
++ << " exp " << exposure_ << " again " << again_
++ << " gain R/B " << params_->gainR << "/" << params_->gainB;
++}
++
++/* DENOMINATOR of 10 gives ~10% increment/decrement; DENOMINATOR of 5 - about ~20% */
++#define DENOMINATOR 10
++#define UP_NUMERATOR (DENOMINATOR + 1)
++#define DOWN_NUMERATOR (DENOMINATOR - 1)
++
++void IPASoftSimple::update_exposure(double exposuremsv)
++{
++ int next;
++
++ if (exposuremsv < EXPOSURE_OPTIMAL_VALUE - EXPOSURE_SATISFACTORY_OFFSET) {
++ next = exposure_ * UP_NUMERATOR / DENOMINATOR;
++ if (next - exposure_ < 1)
++ exposure_ += 1;
++ else
++ exposure_ = next;
++ if (exposure_ >= exposure_max_) {
++ next = again_ * UP_NUMERATOR / DENOMINATOR;
++ if (next - again_ < 1)
++ again_ += 1;
++ else
++ again_ = next;
++ }
++ }
++
++ if (exposuremsv > EXPOSURE_OPTIMAL_VALUE + EXPOSURE_SATISFACTORY_OFFSET) {
++ if (exposure_ == exposure_max_ && again_ != again_min_) {
++ next = again_ * DOWN_NUMERATOR / DENOMINATOR;
++ if (again_ - next < 1)
++ again_ -= 1;
++ else
++ again_ = next;
++ } else {
++ next = exposure_ * DOWN_NUMERATOR / DENOMINATOR;
++ if (exposure_ - next < 1)
++ exposure_ -= 1;
++ else
++ exposure_ = next;
++ }
++ }
++
++ if (exposure_ > exposure_max_)
++ exposure_ = exposure_max_;
++ else if (exposure_ < exposure_min_)
++ exposure_ = exposure_min_;
++
++ if (again_ > again_max_)
++ again_ = again_max_;
++ else if (again_ < again_min_)
++ again_ = again_min_;
++}
++
++} /* namespace ipa::soft */
++
++/*
++ * External IPA module interface
++ */
++extern "C" {
++const struct IPAModuleInfo ipaModuleInfo = {
++ IPA_MODULE_API_VERSION,
++ 0,
++ "SimplePipelineHandler",
++ "soft/simple",
++};
++
++IPAInterface *ipaCreate()
++{
++ return new ipa::soft::IPASoftSimple();
++}
++
++} /* extern "C" */
++
++} /* namespace libcamera */
+--
+2.43.0
+
diff --git a/nix/pkgs/libcamera/0013-libcamera-software_isp-add-Simple-SoftwareIsp-implem.patch b/nix/pkgs/libcamera/0013-libcamera-software_isp-add-Simple-SoftwareIsp-implem.patch
new file mode 100644
index 0000000..24636ed
--- /dev/null
+++ b/nix/pkgs/libcamera/0013-libcamera-software_isp-add-Simple-SoftwareIsp-implem.patch
@@ -0,0 +1,483 @@
+From 21f1dd954a44b4e8f81abbfea276e4b60f8a8297 Mon Sep 17 00:00:00 2001
+From: Andrey Konovalov <andrey.konovalov@linaro.org>
+Date: Thu, 23 Nov 2023 16:47:15 +0300
+Subject: [PATCH 13/25] libcamera: software_isp: add Simple SoftwareIsp
+ implementation
+
+The implementation of SoftwareIsp handles creation of Soft IPA
+and interactions with it, so that the pipeline handler wouldn't
+need to care about the Soft IPA.
+
+Doxygen documentation by Dennis Bonke.
+
+Signed-off-by: Andrey Konovalov <andrey.konovalov@linaro.org>
+Co-authored-by: Dennis Bonke <admin@dennisbonke.com>
+Signed-off-by: Dennis Bonke <admin@dennisbonke.com>
+Co-authored-by: Hans de Goede <hdegoede@redhat.com>
+Signed-off-by: Hans de Goede <hdegoede@redhat.com>
+Tested-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org> # sc8280xp Lenovo x13s
+Tested-by: Pavel Machek <pavel@ucw.cz>
+---
+ .../internal/software_isp/meson.build | 1 +
+ .../internal/software_isp/swisp_simple.h | 163 ++++++++++++
+ src/libcamera/software_isp/meson.build | 19 ++
+ src/libcamera/software_isp/swisp_simple.cpp | 238 ++++++++++++++++++
+ 4 files changed, 421 insertions(+)
+ create mode 100644 include/libcamera/internal/software_isp/swisp_simple.h
+ create mode 100644 src/libcamera/software_isp/swisp_simple.cpp
+
+diff --git a/include/libcamera/internal/software_isp/meson.build b/include/libcamera/internal/software_isp/meson.build
+index b5a0d737..cf21ace5 100644
+--- a/include/libcamera/internal/software_isp/meson.build
++++ b/include/libcamera/internal/software_isp/meson.build
+@@ -4,6 +4,7 @@ libcamera_internal_headers += files([
+ 'debayer.h',
+ 'debayer_cpu.h',
+ 'debayer_params.h',
++ 'swisp_simple.h',
+ 'swisp_stats.h',
+ 'swstats.h',
+ 'swstats_cpu.h',
+diff --git a/include/libcamera/internal/software_isp/swisp_simple.h b/include/libcamera/internal/software_isp/swisp_simple.h
+new file mode 100644
+index 00000000..87613c23
+--- /dev/null
++++ b/include/libcamera/internal/software_isp/swisp_simple.h
+@@ -0,0 +1,163 @@
++/* SPDX-License-Identifier: LGPL-2.1-or-later */
++/*
++ * Copyright (C) 2023, Linaro Ltd
++ *
++ * swisp_simple.h - Simple software ISP implementation
++ */
++
++#pragma once
++
++#include <libcamera/base/log.h>
++#include <libcamera/base/thread.h>
++
++#include <libcamera/pixel_format.h>
++
++#include <libcamera/ipa/soft_ipa_interface.h>
++#include <libcamera/ipa/soft_ipa_proxy.h>
++
++#include "libcamera/internal/dma_heaps.h"
++#include "libcamera/internal/software_isp.h"
++#include "libcamera/internal/software_isp/debayer_cpu.h"
++
++namespace libcamera {
++
++/**
++ * \brief Class for the Simple Software ISP.
++ *
++ * Implementation of the SoftwareIsp interface.
++ */
++class SwIspSimple : public SoftwareIsp
++{
++public:
++ /**
++ * \brief Constructor for the SwIspSimple object.
++ *
++ * \param[in] pipe The pipeline handler in use.
++ * \param[in] sensorControls The sensor controls.
++ */
++ SwIspSimple(PipelineHandler *pipe, const ControlInfoMap &sensorControls);
++ ~SwIspSimple() {}
++
++ /**
++ * \brief Load a configuration from a file.
++ * \param[in] filename The file to load from.
++ *
++ * \return 0 on success.
++ */
++ int loadConfiguration([[maybe_unused]] const std::string &filename) { return 0; }
++
++ /**
++ * \brief Gets if there is a valid debayer object.
++ *
++ * \returns true if there is, false otherwise.
++ */
++ bool isValid() const;
++
++ /**
++ * \brief Get the supported output formats.
++ * \param[in] input The input format.
++ *
++ * \return all supported output formats or an empty vector if there are none.
++ */
++ std::vector<PixelFormat> formats(PixelFormat input);
++
++ /**
++ * \brief Get the supported output sizes for the given input format and size.
++ * \param[in] inputFormat The input format.
++ * \param[in] inputSize The input size.
++ *
++ * \return The valid size ranges or an empty range if there are none.
++ */
++ SizeRange sizes(PixelFormat inputFormat, const Size &inputSize);
++
++ /**
++ * \brief Get the stride and the frame size.
++ * \param[in] outputFormat The output format.
++ * \param[in] size The output size.
++ *
++ * \return a tuple of the stride and the frame size, or a tuple with 0,0 if there is no valid output config.
++ */
++ std::tuple<unsigned int, unsigned int>
++ strideAndFrameSize(const PixelFormat &outputFormat, const Size &size);
++
++ /**
++ * \brief Configure the SwIspSimple object according to the passed in parameters.
++ * \param[in] inputCfg The input configuration.
++ * \param[in] outputCfgs The output configurations.
++ * \param[in] sensorControls The sensor controls.
++ *
++ * \return 0 on success, a negative errno on failure.
++ */
++ int configure(const StreamConfiguration &inputCfg,
++ const std::vector<std::reference_wrapper<StreamConfiguration>> &outputCfgs,
++ const ControlInfoMap &sensorControls);
++
++ /**
++ * \brief Exports the buffers for use in processing.
++ * \param[in] output The number of outputs requested.
++ * \param[in] count The number of planes.
++ * \param[out] buffers The exported buffers.
++ *
++ * \return count when successful, a negative return value if an error occurred.
++ */
++ int exportBuffers(unsigned int output, unsigned int count,
++ std::vector<std::unique_ptr<FrameBuffer>> *buffers);
++
++ /**
++ * \brief Process the statistics gathered.
++ * \param[in] sensorControls The sensor controls.
++ */
++ void processStats(const ControlList &sensorControls);
++
++ /**
++ * \brief Starts the Software ISP worker.
++ *
++ * \return 0 on success, any other value indicates an error.
++ */
++ int start();
++
++ /**
++ * \brief Stops the Software ISP worker.
++ */
++ void stop();
++
++ /**
++ * \brief Queues buffers for processing.
++ * \param[in] input The input framebuffer.
++ * \param[in] outputs The output framebuffers.
++ *
++ * \return 0 on success, a negative errno on failure
++ */
++ int queueBuffers(FrameBuffer *input,
++ const std::map<unsigned int, FrameBuffer *> &outputs);
++
++ /**
++ * \brief Get the signal for when the sensor controls are set.
++ *
++ * \return The control list of the sensor controls.
++ */
++ Signal<const ControlList &> &getSignalSetSensorControls();
++
++ /**
++ * \brief Process the input framebuffer.
++ * \param[in] input The input framebuffer.
++ * \param[out] output The output framebuffer.
++ */
++ void process(FrameBuffer *input, FrameBuffer *output);
++
++private:
++ void saveIspParams(int dummy);
++ void statsReady(int dummy);
++ void inputReady(FrameBuffer *input);
++ void outputReady(FrameBuffer *output);
++
++ std::unique_ptr<DebayerCpu> debayer_;
++ Thread ispWorkerThread_;
++ SharedMemObject<DebayerParams> sharedParams_;
++ DebayerParams debayerParams_;
++ DmaHeap dmaHeap_;
++
++ std::unique_ptr<ipa::soft::IPAProxySoft> ipa_;
++};
++
++} /* namespace libcamera */
+diff --git a/src/libcamera/software_isp/meson.build b/src/libcamera/software_isp/meson.build
+index 6d7a44d7..9464f2fd 100644
+--- a/src/libcamera/software_isp/meson.build
++++ b/src/libcamera/software_isp/meson.build
+@@ -6,3 +6,22 @@ libcamera_sources += files([
+ 'swstats.cpp',
+ 'swstats_cpu.cpp',
+ ])
++
++# Software ISP is enabled for 'simple' pipeline handler.
++# The 'pipelines' option value should be
++# 'simple/<Software ISP implementation>' e.g.:
++# -Dpipelines=simple/simple
++# The source file should be named swisp_<Software ISP implementation>.cpp,
++# e.g. 'swisp_simple.cpp'.
++
++foreach pipeline : pipelines
++ pipeline = pipeline.split('/')
++ if pipeline.length() == 2 and pipeline[0] == 'simple'
++ libcamera_sources += files([
++ 'swisp_' + pipeline[1] + '.cpp',
++ ])
++ # the 'break' below can be removed if/when multiple
++ # Software ISP implementations are allowed in single build
++ break
++ endif
++endforeach
+diff --git a/src/libcamera/software_isp/swisp_simple.cpp b/src/libcamera/software_isp/swisp_simple.cpp
+new file mode 100644
+index 00000000..0884166e
+--- /dev/null
++++ b/src/libcamera/software_isp/swisp_simple.cpp
+@@ -0,0 +1,238 @@
++/* SPDX-License-Identifier: LGPL-2.1-or-later */
++/*
++ * Copyright (C) 2023, Linaro Ltd
++ *
++ * swisp_simple.cpp - Simple software ISP implementation
++ */
++
++#include "libcamera/internal/software_isp/swisp_simple.h"
++
++#include <sys/mman.h>
++#include <sys/types.h>
++#include <unistd.h>
++
++#include <libcamera/formats.h>
++#include <libcamera/stream.h>
++
++#include "libcamera/internal/bayer_format.h"
++#include "libcamera/internal/framebuffer.h"
++#include "libcamera/internal/ipa_manager.h"
++#include "libcamera/internal/mapped_framebuffer.h"
++
++namespace libcamera {
++
++SwIspSimple::SwIspSimple(PipelineHandler *pipe, const ControlInfoMap &sensorControls)
++ : SoftwareIsp(pipe, sensorControls), debayer_(nullptr), debayerParams_{ 256, 256, 256, 0.5f },
++ dmaHeap_(DmaHeap::DmaHeapFlag::Cma | DmaHeap::DmaHeapFlag::System)
++{
++ if (!dmaHeap_.isValid()) {
++ LOG(SoftwareIsp, Error) << "Failed to create DmaHeap object";
++ return;
++ }
++
++ sharedParams_ = SharedMemObject<DebayerParams>("softIsp_params");
++ if (!sharedParams_.fd().isValid()) {
++ LOG(SoftwareIsp, Error) << "Failed to create shared memory for parameters";
++ return;
++ }
++
++ std::unique_ptr<SwStatsCpu> stats;
++
++ stats = std::make_unique<SwStatsCpu>();
++ if (!stats) {
++ LOG(SoftwareIsp, Error) << "Failed to create SwStatsCpu object";
++ return;
++ }
++
++ stats->statsReady.connect(this, &SwIspSimple::statsReady);
++
++ debayer_ = std::make_unique<DebayerCpu>(std::move(stats));
++ if (!debayer_) {
++ LOG(SoftwareIsp, Error) << "Failed to create DebayerCpu object";
++ return;
++ }
++
++ debayer_->inputBufferReady.connect(this, &SwIspSimple::inputReady);
++ debayer_->outputBufferReady.connect(this, &SwIspSimple::outputReady);
++
++ ipa_ = IPAManager::createIPA<ipa::soft::IPAProxySoft>(pipe, 0, 0);
++ if (!ipa_) {
++ LOG(SoftwareIsp, Error)
++ << "Creating IPA for software ISP failed";
++ debayer_.reset();
++ return;
++ }
++
++ int ret = ipa_->init(IPASettings{ "No cfg file", "No sensor model" },
++ debayer_->getStatsFD(),
++ sharedParams_.fd(),
++ sensorControls);
++ if (ret) {
++ LOG(SoftwareIsp, Error) << "IPA init failed";
++ debayer_.reset();
++ return;
++ }
++
++ ipa_->setIspParams.connect(this, &SwIspSimple::saveIspParams);
++
++ debayer_->moveToThread(&ispWorkerThread_);
++}
++
++void SwIspSimple::processStats(const ControlList &sensorControls)
++{
++ ASSERT(ipa_);
++ ipa_->processStats(sensorControls);
++}
++
++Signal<const ControlList &> &SwIspSimple::getSignalSetSensorControls()
++{
++ ASSERT(ipa_);
++ return ipa_->setSensorControls;
++}
++
++bool SwIspSimple::isValid() const
++{
++ return !!debayer_;
++}
++
++std::vector<PixelFormat> SwIspSimple::formats(PixelFormat inputFormat)
++{
++ ASSERT(debayer_ != nullptr);
++
++ return debayer_->formats(inputFormat);
++}
++
++SizeRange SwIspSimple::sizes(PixelFormat inputFormat, const Size &inputSize)
++{
++ ASSERT(debayer_ != nullptr);
++
++ return debayer_->sizes(inputFormat, inputSize);
++}
++
++std::tuple<unsigned int, unsigned int>
++SwIspSimple::strideAndFrameSize(const PixelFormat &outputFormat, const Size &size)
++{
++ ASSERT(debayer_ != nullptr);
++
++ return debayer_->strideAndFrameSize(outputFormat, size);
++}
++
++int SwIspSimple::configure(const StreamConfiguration &inputCfg,
++ const std::vector<std::reference_wrapper<StreamConfiguration>> &outputCfgs,
++ const ControlInfoMap &sensorControls)
++{
++ ASSERT(ipa_ != nullptr && debayer_ != nullptr);
++
++ int ret = ipa_->configure(sensorControls);
++ if (ret < 0)
++ return ret;
++
++ return debayer_->configure(inputCfg, outputCfgs);
++}
++
++int SwIspSimple::exportBuffers(unsigned int output, unsigned int count,
++ std::vector<std::unique_ptr<FrameBuffer>> *buffers)
++{
++ ASSERT(debayer_ != nullptr);
++
++ /* single output for now */
++ if (output >= 1)
++ return -EINVAL;
++
++ for (unsigned int i = 0; i < count; i++) {
++ const std::string name = "frame-" + std::to_string(i);
++ const size_t frameSize = debayer_->frameSize();
++
++ FrameBuffer::Plane outPlane;
++ outPlane.fd = SharedFD(dmaHeap_.alloc(name.c_str(), frameSize));
++ if (!outPlane.fd.isValid()) {
++ LOG(SoftwareIsp, Error)
++ << "failed to allocate a dma_buf";
++ return -ENOMEM;
++ }
++ outPlane.offset = 0;
++ outPlane.length = frameSize;
++
++ std::vector<FrameBuffer::Plane> planes{ outPlane };
++ buffers->emplace_back(std::make_unique<FrameBuffer>(std::move(planes)));
++ }
++
++ return count;
++}
++
++int SwIspSimple::queueBuffers(FrameBuffer *input,
++ const std::map<unsigned int, FrameBuffer *> &outputs)
++{
++ unsigned int mask = 0;
++
++ /*
++ * Validate the outputs as a sanity check: at least one output is
++ * required, all outputs must reference a valid stream and no two
++ * outputs can reference the same stream.
++ */
++ if (outputs.empty())
++ return -EINVAL;
++
++ for (auto [index, buffer] : outputs) {
++ if (!buffer)
++ return -EINVAL;
++ if (index >= 1) /* only single stream atm */
++ return -EINVAL;
++ if (mask & (1 << index))
++ return -EINVAL;
++
++ mask |= 1 << index;
++ }
++
++ process(input, outputs.at(0));
++
++ return 0;
++}
++
++int SwIspSimple::start()
++{
++ int ret = ipa_->start();
++ if (ret)
++ return ret;
++
++ ispWorkerThread_.start();
++ return 0;
++}
++
++void SwIspSimple::stop()
++{
++ ispWorkerThread_.exit();
++ ispWorkerThread_.wait();
++
++ ipa_->stop();
++}
++
++void SwIspSimple::process(FrameBuffer *input, FrameBuffer *output)
++{
++ debayer_->invokeMethod(&DebayerCpu::process,
++ ConnectionTypeQueued, input, output, debayerParams_);
++}
++
++void SwIspSimple::saveIspParams([[maybe_unused]] int dummy)
++{
++ debayerParams_ = *sharedParams_;
++}
++
++void SwIspSimple::statsReady(int dummy)
++{
++ ispStatsReady.emit(dummy);
++}
++
++void SwIspSimple::inputReady(FrameBuffer *input)
++{
++ inputBufferReady.emit(input);
++}
++
++void SwIspSimple::outputReady(FrameBuffer *output)
++{
++ outputBufferReady.emit(output);
++}
++
++REGISTER_SOFTWAREISP(SwIspSimple)
++
++} /* namespace libcamera */
+--
+2.43.0
+
diff --git a/nix/pkgs/libcamera/0014-libcamera-pipeline-simple-rename-converterBuffers_-a.patch b/nix/pkgs/libcamera/0014-libcamera-pipeline-simple-rename-converterBuffers_-a.patch
new file mode 100644
index 0000000..fc603fc
--- /dev/null
+++ b/nix/pkgs/libcamera/0014-libcamera-pipeline-simple-rename-converterBuffers_-a.patch
@@ -0,0 +1,238 @@
+From 155065b3d78c14173fd71c21fe0639f94c3da109 Mon Sep 17 00:00:00 2001
+From: Andrey Konovalov <andrey.konovalov@linaro.org>
+Date: Fri, 1 Dec 2023 15:45:14 +0300
+Subject: [PATCH 14/25] libcamera: pipeline: simple: rename converterBuffers_
+ and related vars
+
+The converterBuffers_ and the converterQueue_ are not that specific
+to the Converter, and could be used by another entity doing the format
+conversion.
+
+Rename converterBuffers_, converterQueue_, and useConverter_ to
+conversionBuffers_, conversionQueue_ and useConversion_ to
+disassociate them from the Converter.
+
+Signed-off-by: Andrey Konovalov <andrey.konovalov@linaro.org>
+Signed-off-by: Hans de Goede <hdegoede@redhat.com>
+Tested-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org> # sc8280xp Lenovo x13s
+Tested-by: Pavel Machek <pavel@ucw.cz>
+---
+ src/libcamera/pipeline/simple/simple.cpp | 63 ++++++++++++------------
+ 1 file changed, 32 insertions(+), 31 deletions(-)
+
+diff --git a/src/libcamera/pipeline/simple/simple.cpp b/src/libcamera/pipeline/simple/simple.cpp
+index 4d0e7255..fa298746 100644
+--- a/src/libcamera/pipeline/simple/simple.cpp
++++ b/src/libcamera/pipeline/simple/simple.cpp
+@@ -269,17 +269,18 @@ public:
+ std::vector<Configuration> configs_;
+ std::map<PixelFormat, std::vector<const Configuration *>> formats_;
+
++ std::vector<std::unique_ptr<FrameBuffer>> conversionBuffers_;
++ std::queue<std::map<unsigned int, FrameBuffer *>> conversionQueue_;
++ bool useConversion_;
++
+ std::unique_ptr<Converter> converter_;
+- std::vector<std::unique_ptr<FrameBuffer>> converterBuffers_;
+- bool useConverter_;
+- std::queue<std::map<unsigned int, FrameBuffer *>> converterQueue_;
+
+ private:
+ void tryPipeline(unsigned int code, const Size &size);
+ static std::vector<const MediaPad *> routedSourcePads(MediaPad *sink);
+
+- void converterInputDone(FrameBuffer *buffer);
+- void converterOutputDone(FrameBuffer *buffer);
++ void conversionInputDone(FrameBuffer *buffer);
++ void conversionOutputDone(FrameBuffer *buffer);
+ };
+
+ class SimpleCameraConfiguration : public CameraConfiguration
+@@ -503,8 +504,8 @@ int SimpleCameraData::init()
+ << "Failed to create converter, disabling format conversion";
+ converter_.reset();
+ } else {
+- converter_->inputBufferReady.connect(this, &SimpleCameraData::converterInputDone);
+- converter_->outputBufferReady.connect(this, &SimpleCameraData::converterOutputDone);
++ converter_->inputBufferReady.connect(this, &SimpleCameraData::conversionInputDone);
++ converter_->outputBufferReady.connect(this, &SimpleCameraData::conversionOutputDone);
+ }
+ }
+
+@@ -740,7 +741,7 @@ void SimpleCameraData::bufferReady(FrameBuffer *buffer)
+ * point converting an erroneous buffer.
+ */
+ if (buffer->metadata().status != FrameMetadata::FrameSuccess) {
+- if (!useConverter_) {
++ if (!useConversion_) {
+ /* No conversion, just complete the request. */
+ Request *request = buffer->request();
+ pipe->completeBuffer(request, buffer);
+@@ -756,16 +757,16 @@ void SimpleCameraData::bufferReady(FrameBuffer *buffer)
+ if (buffer->metadata().status != FrameMetadata::FrameCancelled)
+ video_->queueBuffer(buffer);
+
+- if (converterQueue_.empty())
++ if (conversionQueue_.empty())
+ return;
+
+ Request *request = nullptr;
+- for (auto &item : converterQueue_.front()) {
++ for (auto &item : conversionQueue_.front()) {
+ FrameBuffer *outputBuffer = item.second;
+ request = outputBuffer->request();
+ pipe->completeBuffer(request, outputBuffer);
+ }
+- converterQueue_.pop();
++ conversionQueue_.pop();
+
+ if (request)
+ pipe->completeRequest(request);
+@@ -782,9 +783,9 @@ void SimpleCameraData::bufferReady(FrameBuffer *buffer)
+ */
+ Request *request = buffer->request();
+
+- if (useConverter_ && !converterQueue_.empty()) {
++ if (useConversion_ && !conversionQueue_.empty()) {
+ const std::map<unsigned int, FrameBuffer *> &outputs =
+- converterQueue_.front();
++ conversionQueue_.front();
+ if (!outputs.empty()) {
+ FrameBuffer *outputBuffer = outputs.begin()->second;
+ if (outputBuffer)
+@@ -801,14 +802,14 @@ void SimpleCameraData::bufferReady(FrameBuffer *buffer)
+ * conversion is needed. If there's no queued request, just requeue the
+ * captured buffer for capture.
+ */
+- if (useConverter_) {
+- if (converterQueue_.empty()) {
++ if (useConversion_) {
++ if (conversionQueue_.empty()) {
+ video_->queueBuffer(buffer);
+ return;
+ }
+
+- converter_->queueBuffers(buffer, converterQueue_.front());
+- converterQueue_.pop();
++ converter_->queueBuffers(buffer, conversionQueue_.front());
++ conversionQueue_.pop();
+ return;
+ }
+
+@@ -817,13 +818,13 @@ void SimpleCameraData::bufferReady(FrameBuffer *buffer)
+ pipe->completeRequest(request);
+ }
+
+-void SimpleCameraData::converterInputDone(FrameBuffer *buffer)
++void SimpleCameraData::conversionInputDone(FrameBuffer *buffer)
+ {
+ /* Queue the input buffer back for capture. */
+ video_->queueBuffer(buffer);
+ }
+
+-void SimpleCameraData::converterOutputDone(FrameBuffer *buffer)
++void SimpleCameraData::conversionOutputDone(FrameBuffer *buffer)
+ {
+ SimplePipelineHandler *pipe = SimpleCameraData::pipe();
+
+@@ -1159,14 +1160,14 @@ int SimplePipelineHandler::configure(Camera *camera, CameraConfiguration *c)
+
+ /* Configure the converter if needed. */
+ std::vector<std::reference_wrapper<StreamConfiguration>> outputCfgs;
+- data->useConverter_ = config->needConversion();
++ data->useConversion_ = config->needConversion();
+
+ for (unsigned int i = 0; i < config->size(); ++i) {
+ StreamConfiguration &cfg = config->at(i);
+
+ cfg.setStream(&data->streams_[i]);
+
+- if (data->useConverter_)
++ if (data->useConversion_)
+ outputCfgs.push_back(cfg);
+ }
+
+@@ -1192,7 +1193,7 @@ int SimplePipelineHandler::exportFrameBuffers(Camera *camera, Stream *stream,
+ * Export buffers on the converter or capture video node, depending on
+ * whether the converter is used or not.
+ */
+- if (data->useConverter_)
++ if (data->useConversion_)
+ return data->converter_->exportBuffers(data->streamIndex(stream),
+ count, buffers);
+ else
+@@ -1213,13 +1214,13 @@ int SimplePipelineHandler::start(Camera *camera, [[maybe_unused]] const ControlL
+ return -EBUSY;
+ }
+
+- if (data->useConverter_) {
++ if (data->useConversion_) {
+ /*
+ * When using the converter allocate a fixed number of internal
+ * buffers.
+ */
+ ret = video->allocateBuffers(kNumInternalBuffers,
+- &data->converterBuffers_);
++ &data->conversionBuffers_);
+ } else {
+ /* Otherwise, prepare for using buffers from the only stream. */
+ Stream *stream = &data->streams_[0];
+@@ -1238,7 +1239,7 @@ int SimplePipelineHandler::start(Camera *camera, [[maybe_unused]] const ControlL
+ return ret;
+ }
+
+- if (data->useConverter_) {
++ if (data->useConversion_) {
+ ret = data->converter_->start();
+ if (ret < 0) {
+ stop(camera);
+@@ -1246,7 +1247,7 @@ int SimplePipelineHandler::start(Camera *camera, [[maybe_unused]] const ControlL
+ }
+
+ /* Queue all internal buffers for capture. */
+- for (std::unique_ptr<FrameBuffer> &buffer : data->converterBuffers_)
++ for (std::unique_ptr<FrameBuffer> &buffer : data->conversionBuffers_)
+ video->queueBuffer(buffer.get());
+ }
+
+@@ -1258,7 +1259,7 @@ void SimplePipelineHandler::stopDevice(Camera *camera)
+ SimpleCameraData *data = cameraData(camera);
+ V4L2VideoDevice *video = data->video_;
+
+- if (data->useConverter_)
++ if (data->useConversion_)
+ data->converter_->stop();
+
+ video->streamOff();
+@@ -1266,7 +1267,7 @@ void SimplePipelineHandler::stopDevice(Camera *camera)
+
+ video->bufferReady.disconnect(data, &SimpleCameraData::bufferReady);
+
+- data->converterBuffers_.clear();
++ data->conversionBuffers_.clear();
+
+ releasePipeline(data);
+ }
+@@ -1284,7 +1285,7 @@ int SimplePipelineHandler::queueRequestDevice(Camera *camera, Request *request)
+ * queue, it will be handed to the converter in the capture
+ * completion handler.
+ */
+- if (data->useConverter_) {
++ if (data->useConversion_) {
+ buffers.emplace(data->streamIndex(stream), buffer);
+ } else {
+ ret = data->video_->queueBuffer(buffer);
+@@ -1293,8 +1294,8 @@ int SimplePipelineHandler::queueRequestDevice(Camera *camera, Request *request)
+ }
+ }
+
+- if (data->useConverter_)
+- data->converterQueue_.push(std::move(buffers));
++ if (data->useConversion_)
++ data->conversionQueue_.push(std::move(buffers));
+
+ return 0;
+ }
+--
+2.43.0
+
diff --git a/nix/pkgs/libcamera/0015-libcamera-pipeline-simple-enable-use-of-Soft-ISP-and.patch b/nix/pkgs/libcamera/0015-libcamera-pipeline-simple-enable-use-of-Soft-ISP-and.patch
new file mode 100644
index 0000000..f639a3b
--- /dev/null
+++ b/nix/pkgs/libcamera/0015-libcamera-pipeline-simple-enable-use-of-Soft-ISP-and.patch
@@ -0,0 +1,243 @@
+From ad61052d9aea1f1af6aaef9ce7e5d447c595187c Mon Sep 17 00:00:00 2001
+From: Andrey Konovalov <andrey.konovalov@linaro.org>
+Date: Tue, 12 Dec 2023 02:02:37 +0300
+Subject: [PATCH 15/25] libcamera: pipeline: simple: enable use of Soft ISP and
+ Soft IPA
+
+To enable the Simple Soft ISP and Soft IPA for simple pipeline handler
+configure the build with:
+ -Dpipelines=simple/simple -Dipas=simple/simple
+
+If the pipeline uses Converter, Soft ISP and Soft IPA aren't
+available.
+
+Configuring the build with just:
+ -Dpipelines=simple
+makes it to work like before - Soft ISP and Soft IPA aren't used.
+
+Signed-off-by: Andrey Konovalov <andrey.konovalov@linaro.org>
+Signed-off-by: Hans de Goede <hdegoede@redhat.com>
+Tested-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org> # sc8280xp Lenovo x13s
+Tested-by: Pavel Machek <pavel@ucw.cz>
+---
+ src/libcamera/pipeline/simple/simple.cpp | 111 ++++++++++++++++++-----
+ 1 file changed, 89 insertions(+), 22 deletions(-)
+
+diff --git a/src/libcamera/pipeline/simple/simple.cpp b/src/libcamera/pipeline/simple/simple.cpp
+index fa298746..c76510c2 100644
+--- a/src/libcamera/pipeline/simple/simple.cpp
++++ b/src/libcamera/pipeline/simple/simple.cpp
+@@ -34,6 +34,7 @@
+ #include "libcamera/internal/device_enumerator.h"
+ #include "libcamera/internal/media_device.h"
+ #include "libcamera/internal/pipeline_handler.h"
++#include "libcamera/internal/software_isp.h"
+ #include "libcamera/internal/v4l2_subdevice.h"
+ #include "libcamera/internal/v4l2_videodevice.h"
+
+@@ -274,6 +275,7 @@ public:
+ bool useConversion_;
+
+ std::unique_ptr<Converter> converter_;
++ std::unique_ptr<SoftwareIsp> swIsp_;
+
+ private:
+ void tryPipeline(unsigned int code, const Size &size);
+@@ -281,6 +283,9 @@ private:
+
+ void conversionInputDone(FrameBuffer *buffer);
+ void conversionOutputDone(FrameBuffer *buffer);
++
++ void ispStatsReady(int dummy);
++ void setSensorControls(const ControlList &sensorControls);
+ };
+
+ class SimpleCameraConfiguration : public CameraConfiguration
+@@ -509,6 +514,25 @@ int SimpleCameraData::init()
+ }
+ }
+
++ /*
++ * Create SoftwareIsp unconditionally if no converter is used
++ * - to be revisited
++ */
++ if (!converter_) {
++ swIsp_ = SoftwareIspFactoryBase::create(pipe, sensor_->controls());
++ if (!swIsp_) {
++ LOG(SimplePipeline, Warning)
++ << "Failed to create software ISP, disabling software debayering";
++ swIsp_.reset();
++ } else {
++ swIsp_->inputBufferReady.connect(this, &SimpleCameraData::conversionInputDone);
++ swIsp_->outputBufferReady.connect(this, &SimpleCameraData::conversionOutputDone);
++ swIsp_->ispStatsReady.connect(this, &SimpleCameraData::ispStatsReady);
++
++ swIsp_->getSignalSetSensorControls().connect(this, &SimpleCameraData::setSensorControls);
++ }
++ }
++
+ video_ = pipe->video(entities_.back().entity);
+ ASSERT(video_);
+
+@@ -599,12 +623,20 @@ void SimpleCameraData::tryPipeline(unsigned int code, const Size &size)
+ config.captureFormat = pixelFormat;
+ config.captureSize = format.size;
+
+- if (!converter_) {
+- config.outputFormats = { pixelFormat };
+- config.outputSizes = config.captureSize;
+- } else {
++ if (converter_) {
+ config.outputFormats = converter_->formats(pixelFormat);
+ config.outputSizes = converter_->sizes(format.size);
++ } else if (swIsp_) {
++ config.outputFormats = swIsp_->formats(pixelFormat);
++ config.outputSizes = swIsp_->sizes(pixelFormat, format.size);
++ if (config.outputFormats.empty()) {
++ /* Do not use swIsp for unsupported pixelFormat's */
++ config.outputFormats = { pixelFormat };
++ config.outputSizes = config.captureSize;
++ }
++ } else {
++ config.outputFormats = { pixelFormat };
++ config.outputSizes = config.captureSize;
+ }
+
+ configs_.push_back(config);
+@@ -750,9 +782,9 @@ void SimpleCameraData::bufferReady(FrameBuffer *buffer)
+ }
+
+ /*
+- * The converter is in use. Requeue the internal buffer for
+- * capture (unless the stream is being stopped), and complete
+- * the request with all the user-facing buffers.
++ * The converter or Software ISP is in use. Requeue the internal
++ * buffer for capture (unless the stream is being stopped), and
++ * complete the request with all the user-facing buffers.
+ */
+ if (buffer->metadata().status != FrameMetadata::FrameCancelled)
+ video_->queueBuffer(buffer);
+@@ -798,9 +830,9 @@ void SimpleCameraData::bufferReady(FrameBuffer *buffer)
+ buffer->metadata().timestamp);
+
+ /*
+- * Queue the captured and the request buffer to the converter if format
+- * conversion is needed. If there's no queued request, just requeue the
+- * captured buffer for capture.
++ * Queue the captured and the request buffer to the converter or Software
++ * ISP if format conversion is needed. If there's no queued request, just
++ * requeue the captured buffer for capture.
+ */
+ if (useConversion_) {
+ if (conversionQueue_.empty()) {
+@@ -808,7 +840,11 @@ void SimpleCameraData::bufferReady(FrameBuffer *buffer)
+ return;
+ }
+
+- converter_->queueBuffers(buffer, conversionQueue_.front());
++ if (converter_)
++ converter_->queueBuffers(buffer, conversionQueue_.front());
++ else
++ swIsp_->queueBuffers(buffer, conversionQueue_.front());
++
+ conversionQueue_.pop();
+ return;
+ }
+@@ -834,6 +870,18 @@ void SimpleCameraData::conversionOutputDone(FrameBuffer *buffer)
+ pipe->completeRequest(request);
+ }
+
++void SimpleCameraData::ispStatsReady([[maybe_unused]] int dummy)
++{
++ swIsp_->processStats(sensor_->getControls({ V4L2_CID_ANALOGUE_GAIN,
++ V4L2_CID_EXPOSURE }));
++}
++
++void SimpleCameraData::setSensorControls(const ControlList &sensorControls)
++{
++ ControlList ctrls(sensorControls);
++ sensor_->setControls(&ctrls);
++}
++
+ /* Retrieve all source pads connected to a sink pad through active routes. */
+ std::vector<const MediaPad *> SimpleCameraData::routedSourcePads(MediaPad *sink)
+ {
+@@ -1016,8 +1064,10 @@ CameraConfiguration::Status SimpleCameraConfiguration::validate()
+ /* Set the stride, frameSize and bufferCount. */
+ if (needConversion_) {
+ std::tie(cfg.stride, cfg.frameSize) =
+- data_->converter_->strideAndFrameSize(cfg.pixelFormat,
+- cfg.size);
++ (data_->converter_) ? data_->converter_->strideAndFrameSize(cfg.pixelFormat,
++ cfg.size)
++ : data_->swIsp_->strideAndFrameSize(cfg.pixelFormat,
++ cfg.size);
+ if (cfg.stride == 0)
+ return Invalid;
+ } else {
+@@ -1180,7 +1230,9 @@ int SimplePipelineHandler::configure(Camera *camera, CameraConfiguration *c)
+ inputCfg.stride = captureFormat.planes[0].bpl;
+ inputCfg.bufferCount = kNumInternalBuffers;
+
+- return data->converter_->configure(inputCfg, outputCfgs);
++ return (data->converter_) ? data->converter_->configure(inputCfg, outputCfgs)
++ : data->swIsp_->configure(inputCfg, outputCfgs,
++ data->sensor_->controls());
+ }
+
+ int SimplePipelineHandler::exportFrameBuffers(Camera *camera, Stream *stream,
+@@ -1194,8 +1246,10 @@ int SimplePipelineHandler::exportFrameBuffers(Camera *camera, Stream *stream,
+ * whether the converter is used or not.
+ */
+ if (data->useConversion_)
+- return data->converter_->exportBuffers(data->streamIndex(stream),
+- count, buffers);
++ return (data->converter_) ? data->converter_->exportBuffers(data->streamIndex(stream),
++ count, buffers)
++ : data->swIsp_->exportBuffers(data->streamIndex(stream),
++ count, buffers);
+ else
+ return data->video_->exportBuffers(count, buffers);
+ }
+@@ -1240,10 +1294,18 @@ int SimplePipelineHandler::start(Camera *camera, [[maybe_unused]] const ControlL
+ }
+
+ if (data->useConversion_) {
+- ret = data->converter_->start();
+- if (ret < 0) {
+- stop(camera);
+- return ret;
++ if (data->converter_) {
++ ret = data->converter_->start();
++ if (ret < 0) {
++ stop(camera);
++ return ret;
++ }
++ } else if (data->swIsp_) {
++ ret = data->swIsp_->start();
++ if (ret < 0) {
++ stop(camera);
++ return ret;
++ }
+ }
+
+ /* Queue all internal buffers for capture. */
+@@ -1259,8 +1321,13 @@ void SimplePipelineHandler::stopDevice(Camera *camera)
+ SimpleCameraData *data = cameraData(camera);
+ V4L2VideoDevice *video = data->video_;
+
+- if (data->useConversion_)
+- data->converter_->stop();
++ if (data->useConversion_) {
++ if (data->converter_)
++ data->converter_->stop();
++ else if (data->swIsp_) {
++ data->swIsp_->stop();
++ }
++ }
+
+ video->streamOff();
+ video->releaseBuffers();
+--
+2.43.0
+
diff --git a/nix/pkgs/libcamera/0016-libcamera-swstats_cpu-Add-support-for-8-10-and-12-bp.patch b/nix/pkgs/libcamera/0016-libcamera-swstats_cpu-Add-support-for-8-10-and-12-bp.patch
new file mode 100644
index 0000000..0438885
--- /dev/null
+++ b/nix/pkgs/libcamera/0016-libcamera-swstats_cpu-Add-support-for-8-10-and-12-bp.patch
@@ -0,0 +1,199 @@
+From 66ef9f32e67a96655d10ba38f7a830b3bbfe50f2 Mon Sep 17 00:00:00 2001
+From: Hans de Goede <hdegoede@redhat.com>
+Date: Thu, 14 Dec 2023 19:09:44 +0100
+Subject: [PATCH 16/25] libcamera: swstats_cpu: Add support for 8, 10 and 12
+ bpp unpacked bayer input
+
+Add support for 8, 10 and 12 bpp unpacked bayer input for all 4 standard
+bayer orders.
+
+Tested-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org> # sc8280xp Lenovo x13s
+Tested-by: Pavel Machek <pavel@ucw.cz>
+Signed-off-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
+Signed-off-by: Hans de Goede <hdegoede@redhat.com>
+---
+ .../internal/software_isp/swstats_cpu.h | 9 ++
+ src/libcamera/software_isp/swstats_cpu.cpp | 126 ++++++++++++++++++
+ 2 files changed, 135 insertions(+)
+
+diff --git a/include/libcamera/internal/software_isp/swstats_cpu.h b/include/libcamera/internal/software_isp/swstats_cpu.h
+index 8bb86e98..e7abc6bb 100644
+--- a/include/libcamera/internal/software_isp/swstats_cpu.h
++++ b/include/libcamera/internal/software_isp/swstats_cpu.h
+@@ -11,6 +11,7 @@
+
+ #pragma once
+
++#include "libcamera/internal/bayer_format.h"
+ #include "libcamera/internal/shared_mem_object.h"
+ #include "libcamera/internal/software_isp/swisp_stats.h"
+ #include "libcamera/internal/software_isp/swstats.h"
+@@ -31,6 +32,14 @@ public:
+ const SharedFD &getStatsFD() { return sharedStats_.fd(); }
+ int configure(const StreamConfiguration &inputCfg);
+ private:
++ int setupStandardBayerOrder(BayerFormat::Order order);
++ /* Bayer 8 bpp unpacked */
++ void statsBGGR8Line0(const uint8_t *src[]);
++ /* Bayer 10 bpp unpacked */
++ void statsBGGR10Line0(const uint8_t *src[]);
++ /* Bayer 12 bpp unpacked */
++ void statsBGGR12Line0(const uint8_t *src[]);
++ /* Bayer 10 bpp packed */
+ void statsBGGR10PLine0(const uint8_t *src[]);
+ void statsGBRG10PLine0(const uint8_t *src[]);
+ void resetStats(void);
+diff --git a/src/libcamera/software_isp/swstats_cpu.cpp b/src/libcamera/software_isp/swstats_cpu.cpp
+index 59453d07..87550371 100644
+--- a/src/libcamera/software_isp/swstats_cpu.cpp
++++ b/src/libcamera/software_isp/swstats_cpu.cpp
+@@ -59,6 +59,83 @@ static const unsigned int BLUE_Y_MUL = 29; /* 0.11 * 256 */
+ stats_.sumG_ += sumG; \
+ stats_.sumB_ += sumB;
+
++void SwStatsCpu::statsBGGR8Line0(const uint8_t *src[])
++{
++ const uint8_t *src0 = src[1] + window_.x;
++ const uint8_t *src1 = src[2] + window_.x;
++
++ SWISP_LINARO_START_LINE_STATS(uint8_t)
++
++ if (swap_lines_)
++ std::swap(src0, src1);
++
++ /* x += 4 sample every other 2x2 block */
++ for (int x = 0; x < (int)window_.width; x += 4) {
++ b = src0[x];
++ g = src0[x + 1];
++ g2 = src1[x];
++ r = src1[x + 1];
++
++ g = (g + g2) / 2;
++
++ SWISP_LINARO_ACCUMULATE_LINE_STATS(1)
++ }
++
++ SWISP_LINARO_FINISH_LINE_STATS()
++}
++
++void SwStatsCpu::statsBGGR10Line0(const uint8_t *src[])
++{
++ const uint16_t *src0 = (const uint16_t *)src[1] + window_.x;
++ const uint16_t *src1 = (const uint16_t *)src[2] + window_.x;
++
++ SWISP_LINARO_START_LINE_STATS(uint16_t)
++
++ if (swap_lines_)
++ std::swap(src0, src1);
++
++ /* x += 4 sample every other 2x2 block */
++ for (int x = 0; x < (int)window_.width; x += 4) {
++ b = src0[x];
++ g = src0[x + 1];
++ g2 = src1[x];
++ r = src1[x + 1];
++
++ g = (g + g2) / 2;
++
++ /* divide Y by 4 for 10 -> 8 bpp value */
++ SWISP_LINARO_ACCUMULATE_LINE_STATS(4)
++ }
++
++ SWISP_LINARO_FINISH_LINE_STATS()
++}
++
++void SwStatsCpu::statsBGGR12Line0(const uint8_t *src[])
++{
++ const uint16_t *src0 = (const uint16_t *)src[1] + window_.x;
++ const uint16_t *src1 = (const uint16_t *)src[2] + window_.x;
++
++ SWISP_LINARO_START_LINE_STATS(uint16_t)
++
++ if (swap_lines_)
++ std::swap(src0, src1);
++
++ /* x += 4 sample every other 2x2 block */
++ for (int x = 0; x < (int)window_.width; x += 4) {
++ b = src0[x];
++ g = src0[x + 1];
++ g2 = src1[x];
++ r = src1[x + 1];
++
++ g = (g + g2) / 2;
++
++ /* divide Y by 16 for 12 -> 8 bpp value */
++ SWISP_LINARO_ACCUMULATE_LINE_STATS(16)
++ }
++
++ SWISP_LINARO_FINISH_LINE_STATS()
++}
++
+ static inline __attribute__((always_inline)) void
+ statsBayer10P(const int width, const uint8_t *src0, const uint8_t *src1, bool bggr, SwIspStats &stats_)
+ {
+@@ -124,6 +201,39 @@ void SwStatsCpu::finishStats(void)
+ statsReady.emit(0);
+ }
+
++/*
++ * Check if order is a standard Bayer order and setup x_shift_ and swap_lines_
++ * so that a single BGGR stats function can be used for all 4 standard orders.
++ */
++int SwStatsCpu::setupStandardBayerOrder(BayerFormat::Order order)
++{
++ switch (order) {
++ case BayerFormat::BGGR:
++ x_shift_ = 0;
++ swap_lines_ = false;
++ break;
++ case BayerFormat::GBRG:
++ x_shift_ = 1; /* BGGR -> GBRG */
++ swap_lines_ = false;
++ break;
++ case BayerFormat::GRBG:
++ x_shift_ = 0;
++ swap_lines_ = true; /* BGGR -> GRBG */
++ break;
++ case BayerFormat::RGGB:
++ x_shift_ = 1; /* BGGR -> GBRG */
++ swap_lines_ = true; /* GBRG -> RGGB */
++ break;
++ default:
++ return -EINVAL;
++ }
++
++ patternSize_.height = 2;
++ patternSize_.width = 2;
++ y_skip_mask_ = 0x02; /* Skip every 3th and 4th line */
++ return 0;
++}
++
+ int SwStatsCpu::configure(const StreamConfiguration &inputCfg)
+ {
+ BayerFormat bayerFormat =
+@@ -132,6 +242,22 @@ int SwStatsCpu::configure(const StreamConfiguration &inputCfg)
+ startFrame_ = (SwStats::statsVoidFn)&SwStatsCpu::resetStats;
+ finishFrame_ = (SwStats::statsVoidFn)&SwStatsCpu::finishStats;
+
++ if (bayerFormat.packing == BayerFormat::Packing::None &&
++ setupStandardBayerOrder(bayerFormat.order) == 0) {
++ bpp_ = (bayerFormat.bitDepth + 7) & ~7;
++ switch (bayerFormat.bitDepth) {
++ case 8:
++ stats0_ = (SwStats::statsProcessFn)&SwStatsCpu::statsBGGR8Line0;
++ return 0;
++ case 10:
++ stats0_ = (SwStats::statsProcessFn)&SwStatsCpu::statsBGGR10Line0;
++ return 0;
++ case 12:
++ stats0_ = (SwStats::statsProcessFn)&SwStatsCpu::statsBGGR12Line0;
++ return 0;
++ }
++ }
++
+ if (bayerFormat.bitDepth == 10 &&
+ bayerFormat.packing == BayerFormat::Packing::CSI2) {
+ bpp_ = 10;
+--
+2.43.0
+
diff --git a/nix/pkgs/libcamera/0017-libcamera-debayer_cpu-Add-support-for-8-10-and-12-bp.patch b/nix/pkgs/libcamera/0017-libcamera-debayer_cpu-Add-support-for-8-10-and-12-bp.patch
new file mode 100644
index 0000000..a5a992f
--- /dev/null
+++ b/nix/pkgs/libcamera/0017-libcamera-debayer_cpu-Add-support-for-8-10-and-12-bp.patch
@@ -0,0 +1,237 @@
+From b7b211eb56d98d5b170bd73a23b55aeb45bde8c5 Mon Sep 17 00:00:00 2001
+From: Hans de Goede <hdegoede@redhat.com>
+Date: Thu, 14 Dec 2023 19:57:15 +0100
+Subject: [PATCH 17/25] libcamera: debayer_cpu: Add support for 8, 10 and 12
+ bpp unpacked bayer input
+
+Add support for 8, 10 and 12 bpp unpacked bayer input for all 4 standard
+bayer orders.
+
+Tested-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org> # sc8280xp Lenovo x13s
+Tested-by: Pavel Machek <pavel@ucw.cz>
+Signed-off-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
+Signed-off-by: Hans de Goede <hdegoede@redhat.com>
+---
+ .../internal/software_isp/debayer_cpu.h | 13 ++
+ src/libcamera/software_isp/debayer_cpu.cpp | 128 ++++++++++++++++++
+ 2 files changed, 141 insertions(+)
+
+diff --git a/include/libcamera/internal/software_isp/debayer_cpu.h b/include/libcamera/internal/software_isp/debayer_cpu.h
+index 78573f44..1147b368 100644
+--- a/include/libcamera/internal/software_isp/debayer_cpu.h
++++ b/include/libcamera/internal/software_isp/debayer_cpu.h
+@@ -17,6 +17,7 @@
+
+ #include <libcamera/base/object.h>
+
++#include "libcamera/internal/bayer_format.h"
+ #include "libcamera/internal/software_isp/swstats_cpu.h"
+ #include "libcamera/internal/software_isp/debayer.h"
+
+@@ -75,11 +76,21 @@ public:
+ * \return The output frame size.
+ */
+ unsigned int frameSize() { return outputConfig_.frameSize; }
++
+ private:
+ void initLinePointers(const uint8_t *linePointers[], const uint8_t *src);
+ void shiftLinePointers(const uint8_t *linePointers[], const uint8_t *src);
+ void process2(const uint8_t *src, uint8_t *dst);
+ void process4(const uint8_t *src, uint8_t *dst);
++ /* 8-bit raw bayer format */
++ void debayer8_BGBG_BGR888(uint8_t *dst, const uint8_t *src[]);
++ void debayer8_GRGR_BGR888(uint8_t *dst, const uint8_t *src[]);
++ /* unpacked 10-bit raw bayer format */
++ void debayer10_BGBG_BGR888(uint8_t *dst, const uint8_t *src[]);
++ void debayer10_GRGR_BGR888(uint8_t *dst, const uint8_t *src[]);
++ /* unpacked 12-bit raw bayer format */
++ void debayer12_BGBG_BGR888(uint8_t *dst, const uint8_t *src[]);
++ void debayer12_GRGR_BGR888(uint8_t *dst, const uint8_t *src[]);
+ /* CSI-2 packed 10-bit raw bayer format (all the 4 orders) */
+ void debayer10P_BGBG_BGR888(uint8_t *dst, const uint8_t *src[]);
+ void debayer10P_GRGR_BGR888(uint8_t *dst, const uint8_t *src[]);
+@@ -103,6 +114,7 @@ private:
+
+ int getInputConfig(PixelFormat inputFormat, DebayerInputConfig &config);
+ int getOutputConfig(PixelFormat outputFormat, DebayerOutputConfig &config);
++ int setupStandardBayerOrder(BayerFormat::Order order);
+ int setDebayerFunctions(PixelFormat inputFormat, PixelFormat outputFormat);
+
+ uint8_t gamma_[1024];
+@@ -119,6 +131,7 @@ private:
+ std::unique_ptr<SwStatsCpu> stats_;
+ uint8_t *lineBuffers_[5];
+ unsigned int lineBufferIndex_;
++ unsigned int x_shift_; /* Offset of 0/1 applied to window_.x */
+ bool enableInputMemcpy_;
+ float gamma_correction_;
+ int measuredFrames_;
+diff --git a/src/libcamera/software_isp/debayer_cpu.cpp b/src/libcamera/software_isp/debayer_cpu.cpp
+index e0c3c658..7b7623b7 100644
+--- a/src/libcamera/software_isp/debayer_cpu.cpp
++++ b/src/libcamera/software_isp/debayer_cpu.cpp
+@@ -45,6 +45,11 @@ DebayerCpu::~DebayerCpu()
+ free(lineBuffers_[i]);
+ }
+
++#define DECLARE_SRC_POINTERS(pixel_t) \
++ const pixel_t *prev = (const pixel_t *)src[0] + x_shift_; \
++ const pixel_t *curr = (const pixel_t *)src[1] + x_shift_; \
++ const pixel_t *next = (const pixel_t *)src[2] + x_shift_;
++
+ // RGR
+ // GBG
+ // RGR
+@@ -81,6 +86,70 @@ DebayerCpu::~DebayerCpu()
+ *dst++ = red_[curr[x] / (div)]; \
+ x++;
+
++void DebayerCpu::debayer8_BGBG_BGR888(uint8_t *dst, const uint8_t *src[])
++{
++ DECLARE_SRC_POINTERS(uint8_t)
++
++ for (int x = 0; x < (int)window_.width;) {
++ BGGR_BGR888(1, 1, 1)
++ GBRG_BGR888(1, 1, 1)
++ }
++}
++
++void DebayerCpu::debayer8_GRGR_BGR888(uint8_t *dst, const uint8_t *src[])
++{
++ DECLARE_SRC_POINTERS(uint8_t)
++
++ for (int x = 0; x < (int)window_.width;) {
++ GRBG_BGR888(1, 1, 1)
++ RGGB_BGR888(1, 1, 1)
++ }
++}
++
++void DebayerCpu::debayer10_BGBG_BGR888(uint8_t *dst, const uint8_t *src[])
++{
++ DECLARE_SRC_POINTERS(uint16_t)
++
++ for (int x = 0; x < (int)window_.width;) {
++ /* divide values by 4 for 10 -> 8 bpp value */
++ BGGR_BGR888(1, 1, 4)
++ GBRG_BGR888(1, 1, 4)
++ }
++}
++
++void DebayerCpu::debayer10_GRGR_BGR888(uint8_t *dst, const uint8_t *src[])
++{
++ DECLARE_SRC_POINTERS(uint16_t)
++
++ for (int x = 0; x < (int)window_.width;) {
++ /* divide values by 4 for 10 -> 8 bpp value */
++ GRBG_BGR888(1, 1, 4)
++ RGGB_BGR888(1, 1, 4)
++ }
++}
++
++void DebayerCpu::debayer12_BGBG_BGR888(uint8_t *dst, const uint8_t *src[])
++{
++ DECLARE_SRC_POINTERS(uint16_t)
++
++ for (int x = 0; x < (int)window_.width;) {
++ /* divide values by 16 for 12 -> 8 bpp value */
++ BGGR_BGR888(1, 1, 16)
++ GBRG_BGR888(1, 1, 16)
++ }
++}
++
++void DebayerCpu::debayer12_GRGR_BGR888(uint8_t *dst, const uint8_t *src[])
++{
++ DECLARE_SRC_POINTERS(uint16_t)
++
++ for (int x = 0; x < (int)window_.width;) {
++ /* divide values by 16 for 12 -> 8 bpp value */
++ GRBG_BGR888(1, 1, 16)
++ RGGB_BGR888(1, 1, 16)
++ }
++}
++
+ void DebayerCpu::debayer10P_BGBG_BGR888(uint8_t *dst, const uint8_t *src[])
+ {
+ const int width_in_bytes = window_.width * 5 / 4;
+@@ -170,6 +239,16 @@ int DebayerCpu::getInputConfig(PixelFormat inputFormat, DebayerInputConfig &conf
+ BayerFormat bayerFormat =
+ BayerFormat::fromPixelFormat(inputFormat);
+
++ if ((bayerFormat.bitDepth == 8 || bayerFormat.bitDepth == 10 || bayerFormat.bitDepth == 12) &&
++ bayerFormat.packing == BayerFormat::Packing::None &&
++ isStandardBayerOrder(bayerFormat.order)) {
++ config.bpp = (bayerFormat.bitDepth + 7) & ~7;
++ config.patternSize.width = 2;
++ config.patternSize.height = 2;
++ config.outputFormats = std::vector<PixelFormat>({ formats::RGB888 });
++ return 0;
++ }
++
+ if (bayerFormat.bitDepth == 10 &&
+ bayerFormat.packing == BayerFormat::Packing::CSI2 &&
+ isStandardBayerOrder(bayerFormat.order)) {
+@@ -197,12 +276,61 @@ int DebayerCpu::getOutputConfig(PixelFormat outputFormat, DebayerOutputConfig &c
+ return -EINVAL;
+ }
+
++/*
++ * Check for standard Bayer orders and set x_shift_ and swap debayer0/1, so that
++ * a single pair of BGGR debayer functions can be used for all 4 standard orders.
++ */
++int DebayerCpu::setupStandardBayerOrder(BayerFormat::Order order)
++{
++ switch (order) {
++ case BayerFormat::BGGR:
++ break;
++ case BayerFormat::GBRG:
++ x_shift_ = 1; /* BGGR -> GBRG */
++ break;
++ case BayerFormat::GRBG:
++ std::swap(debayer0_, debayer1_); /* BGGR -> GRBG */
++ break;
++ case BayerFormat::RGGB:
++ x_shift_ = 1; /* BGGR -> GBRG */
++ std::swap(debayer0_, debayer1_); /* GBRG -> RGGB */
++ break;
++ default:
++ return -EINVAL;
++ }
++
++ return 0;
++}
++
+ /* TODO: this ignores outputFormat since there is only 1 supported outputFormat for now */
+ int DebayerCpu::setDebayerFunctions(PixelFormat inputFormat, [[maybe_unused]] PixelFormat outputFormat)
+ {
+ BayerFormat bayerFormat =
+ BayerFormat::fromPixelFormat(inputFormat);
+
++ x_shift_ = 0;
++
++ if ((bayerFormat.bitDepth == 8 || bayerFormat.bitDepth == 10 || bayerFormat.bitDepth == 12) &&
++ bayerFormat.packing == BayerFormat::Packing::None &&
++ isStandardBayerOrder(bayerFormat.order)) {
++ switch (bayerFormat.bitDepth) {
++ case 8:
++ debayer0_ = &DebayerCpu::debayer8_BGBG_BGR888;
++ debayer1_ = &DebayerCpu::debayer8_GRGR_BGR888;
++ break;
++ case 10:
++ debayer0_ = &DebayerCpu::debayer10_BGBG_BGR888;
++ debayer1_ = &DebayerCpu::debayer10_GRGR_BGR888;
++ break;
++ case 12:
++ debayer0_ = &DebayerCpu::debayer12_BGBG_BGR888;
++ debayer1_ = &DebayerCpu::debayer12_GRGR_BGR888;
++ break;
++ }
++ setupStandardBayerOrder(bayerFormat.order);
++ return 0;
++ }
++
+ if (bayerFormat.bitDepth == 10 &&
+ bayerFormat.packing == BayerFormat::Packing::CSI2) {
+ switch (bayerFormat.order) {
+--
+2.43.0
+
diff --git a/nix/pkgs/libcamera/0018-libcamera-debayer_cpu-Add-BGR888-output-support.patch b/nix/pkgs/libcamera/0018-libcamera-debayer_cpu-Add-BGR888-output-support.patch
new file mode 100644
index 0000000..50d826b
--- /dev/null
+++ b/nix/pkgs/libcamera/0018-libcamera-debayer_cpu-Add-BGR888-output-support.patch
@@ -0,0 +1,125 @@
+From b835b2c90785ee02bc98888bf165713d16c24cc4 Mon Sep 17 00:00:00 2001
+From: Hans de Goede <hdegoede@redhat.com>
+Date: Mon, 18 Dec 2023 19:21:07 +0100
+Subject: [PATCH 18/25] libcamera: debayer_cpu: Add BGR888 output support
+
+BGR888 is RGB888 with the red and blue pixels swapped, adjust
+the debayering to swap the red and blue pixels in the bayer pattern
+to add support for writing formats::BGR888.
+
+Signed-off-by: Hans de Goede <hdegoede@redhat.com>
+Tested-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org> # sc8280xp Lenovo x13s
+Tested-by: Pavel Machek <pavel@ucw.cz>
+---
+ .../internal/software_isp/debayer_cpu.h | 1 +
+ src/libcamera/software_isp/debayer_cpu.cpp | 43 ++++++++++++++++---
+ 2 files changed, 39 insertions(+), 5 deletions(-)
+
+diff --git a/include/libcamera/internal/software_isp/debayer_cpu.h b/include/libcamera/internal/software_isp/debayer_cpu.h
+index 1147b368..bdeab7c0 100644
+--- a/include/libcamera/internal/software_isp/debayer_cpu.h
++++ b/include/libcamera/internal/software_isp/debayer_cpu.h
+@@ -133,6 +133,7 @@ private:
+ unsigned int lineBufferIndex_;
+ unsigned int x_shift_; /* Offset of 0/1 applied to window_.x */
+ bool enableInputMemcpy_;
++ bool swapRedBlueGains_;
+ float gamma_correction_;
+ int measuredFrames_;
+ int64_t frameProcessTime_;
+diff --git a/src/libcamera/software_isp/debayer_cpu.cpp b/src/libcamera/software_isp/debayer_cpu.cpp
+index 7b7623b7..0edea4d3 100644
+--- a/src/libcamera/software_isp/debayer_cpu.cpp
++++ b/src/libcamera/software_isp/debayer_cpu.cpp
+@@ -245,7 +245,7 @@ int DebayerCpu::getInputConfig(PixelFormat inputFormat, DebayerInputConfig &conf
+ config.bpp = (bayerFormat.bitDepth + 7) & ~7;
+ config.patternSize.width = 2;
+ config.patternSize.height = 2;
+- config.outputFormats = std::vector<PixelFormat>({ formats::RGB888 });
++ config.outputFormats = std::vector<PixelFormat>({ formats::RGB888, formats::BGR888 });
+ return 0;
+ }
+
+@@ -255,7 +255,7 @@ int DebayerCpu::getInputConfig(PixelFormat inputFormat, DebayerInputConfig &conf
+ config.bpp = 10;
+ config.patternSize.width = 4; /* 5 bytes per *4* pixels */
+ config.patternSize.height = 2;
+- config.outputFormats = std::vector<PixelFormat>({ formats::RGB888 });
++ config.outputFormats = std::vector<PixelFormat>({ formats::RGB888, formats::BGR888 });
+ return 0;
+ }
+
+@@ -266,7 +266,7 @@ int DebayerCpu::getInputConfig(PixelFormat inputFormat, DebayerInputConfig &conf
+
+ int DebayerCpu::getOutputConfig(PixelFormat outputFormat, DebayerOutputConfig &config)
+ {
+- if (outputFormat == formats::RGB888) {
++ if (outputFormat == formats::RGB888 || outputFormat == formats::BGR888) {
+ config.bpp = 24;
+ return 0;
+ }
+@@ -302,12 +302,41 @@ int DebayerCpu::setupStandardBayerOrder(BayerFormat::Order order)
+ return 0;
+ }
+
+-/* TODO: this ignores outputFormat since there is only 1 supported outputFormat for now */
+-int DebayerCpu::setDebayerFunctions(PixelFormat inputFormat, [[maybe_unused]] PixelFormat outputFormat)
++int DebayerCpu::setDebayerFunctions(PixelFormat inputFormat, PixelFormat outputFormat)
+ {
+ BayerFormat bayerFormat =
+ BayerFormat::fromPixelFormat(inputFormat);
+
++ swapRedBlueGains_ = false;
++
++ switch (outputFormat) {
++ case formats::RGB888:
++ break;
++ case formats::BGR888:
++ /* Swap R and B in bayer order to generate BGR888 instead of RGB888 */
++ swapRedBlueGains_ = true;
++
++ switch (bayerFormat.order) {
++ case BayerFormat::BGGR:
++ bayerFormat.order = BayerFormat::RGGB;
++ break;
++ case BayerFormat::GBRG:
++ bayerFormat.order = BayerFormat::GRBG;
++ break;
++ case BayerFormat::GRBG:
++ bayerFormat.order = BayerFormat::GBRG;
++ break;
++ case BayerFormat::RGGB:
++ bayerFormat.order = BayerFormat::BGGR;
++ break;
++ default:
++ goto invalid_fmt;
++ }
++ break;
++ default:
++ goto invalid_fmt;
++ }
++
+ x_shift_ = 0;
+
+ if ((bayerFormat.bitDepth == 8 || bayerFormat.bitDepth == 10 || bayerFormat.bitDepth == 12) &&
+@@ -355,6 +384,7 @@ int DebayerCpu::setDebayerFunctions(PixelFormat inputFormat, [[maybe_unused]] Pi
+ }
+ }
+
++invalid_fmt:
+ LOG(Debayer, Error) << "Unsupported input output format combination";
+ return -EINVAL;
+ }
+@@ -594,6 +624,9 @@ void DebayerCpu::process(FrameBuffer *input, FrameBuffer *output, DebayerParams
+ gamma_correction_ = params.gamma;
+ }
+
++ if (swapRedBlueGains_)
++ std::swap(params.gainR, params.gainB);
++
+ for (int i = 0; i < 256; i++) {
+ int idx;
+
+--
+2.43.0
+
diff --git a/nix/pkgs/libcamera/0019-libcamera-pipeline-simple-Enable-simplepipeline-for-.patch b/nix/pkgs/libcamera/0019-libcamera-pipeline-simple-Enable-simplepipeline-for-.patch
new file mode 100644
index 0000000..cdbd0b9
--- /dev/null
+++ b/nix/pkgs/libcamera/0019-libcamera-pipeline-simple-Enable-simplepipeline-for-.patch
@@ -0,0 +1,30 @@
+From eb45bdfe66af7844a779bc6fcf923cd951336309 Mon Sep 17 00:00:00 2001
+From: Dennis Bonke <admin@dennisbonke.com>
+Date: Fri, 6 Oct 2023 10:39:45 +0200
+Subject: [PATCH 19/25] libcamera: pipeline: simple: Enable simplepipeline for
+ intel-ipu6 DNU
+
+Do Not Upstream, first the ipu6 CSI receiver code needs to land in
+the kernel.
+
+Signed-off-by: Dennis Bonke <admin@dennisbonke.com>
+Signed-off-by: Hans de Goede <hdegoede@redhat.com>
+---
+ src/libcamera/pipeline/simple/simple.cpp | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/src/libcamera/pipeline/simple/simple.cpp b/src/libcamera/pipeline/simple/simple.cpp
+index c76510c2..130843cd 100644
+--- a/src/libcamera/pipeline/simple/simple.cpp
++++ b/src/libcamera/pipeline/simple/simple.cpp
+@@ -197,6 +197,7 @@ static const SimplePipelineInfo supportedDevices[] = {
+ { "mxc-isi", {} },
+ { "qcom-camss", {} },
+ { "sun6i-csi", {} },
++ { "intel-ipu6", {} },
+ };
+
+ } /* namespace */
+--
+2.43.0
+
diff --git a/nix/pkgs/libcamera/0020-libcamera-Add-support-for-IGIG_GBGR_IGIG_GRGB-bayer-.patch b/nix/pkgs/libcamera/0020-libcamera-Add-support-for-IGIG_GBGR_IGIG_GRGB-bayer-.patch
new file mode 100644
index 0000000..768ec07
--- /dev/null
+++ b/nix/pkgs/libcamera/0020-libcamera-Add-support-for-IGIG_GBGR_IGIG_GRGB-bayer-.patch
@@ -0,0 +1,237 @@
+From e03beabbad83c4c283c7f1c2c4798b6c3e2eaf06 Mon Sep 17 00:00:00 2001
+From: Hans de Goede <hdegoede@redhat.com>
+Date: Tue, 19 Dec 2023 11:16:26 +0100
+Subject: [PATCH 20/25] libcamera: Add support for IGIG_GBGR_IGIG_GRGB bayer
+ order DNU
+
+The ov01a1s sensor has the following bayer pattern (4x4 tile repeating):
+
+IGIG
+GBGR
+IGIG
+GRGB
+
+Add support for this PixelFormat to libcamera.
+
+Do Not Upstream, first the include/linux/media-bus-format.h and
+include/linux/videodev2.h changes need to land in the upstream kernel.
+
+Signed-off-by: Hans de Goede <hdegoede@redhat.com>
+---
+ include/libcamera/internal/bayer_format.h | 3 ++-
+ include/linux/drm_fourcc.h | 2 ++
+ include/linux/media-bus-format.h | 4 +++-
+ include/linux/videodev2.h | 3 +++
+ src/libcamera/bayer_format.cpp | 5 +++++
+ src/libcamera/camera_sensor.cpp | 3 +++
+ src/libcamera/formats.cpp | 20 ++++++++++++++++++++
+ src/libcamera/formats.yaml | 5 +++++
+ src/libcamera/v4l2_pixelformat.cpp | 4 ++++
+ src/libcamera/v4l2_subdevice.cpp | 1 +
+ 10 files changed, 48 insertions(+), 2 deletions(-)
+
+diff --git a/include/libcamera/internal/bayer_format.h b/include/libcamera/internal/bayer_format.h
+index 78ba3969..e77106c3 100644
+--- a/include/libcamera/internal/bayer_format.h
++++ b/include/libcamera/internal/bayer_format.h
+@@ -27,7 +27,8 @@ public:
+ GBRG = 1,
+ GRBG = 2,
+ RGGB = 3,
+- MONO = 4
++ MONO = 4,
++ IGIG_GBGR_IGIG_GRGB = 5,
+ };
+
+ enum class Packing : uint16_t {
+diff --git a/include/linux/drm_fourcc.h b/include/linux/drm_fourcc.h
+index 1496e097..750ae8c9 100644
+--- a/include/linux/drm_fourcc.h
++++ b/include/linux/drm_fourcc.h
+@@ -405,6 +405,8 @@ extern "C" {
+ #define DRM_FORMAT_SGRBG10 fourcc_code('B', 'A', '1', '0')
+ #define DRM_FORMAT_SGBRG10 fourcc_code('G', 'B', '1', '0')
+ #define DRM_FORMAT_SBGGR10 fourcc_code('B', 'G', '1', '0')
++/* Mixed 10 bit bayer + ir pixel pattern found on Omnivision ov01a1s */
++#define DRM_FORMAT_SIGIG_GBGR_IGIG_GRGB10 fourcc_code('O', 'V', '1', 'S')
+
+ /* 12-bit Bayer formats */
+ #define DRM_FORMAT_SRGGB12 fourcc_code('R', 'G', '1', '2')
+diff --git a/include/linux/media-bus-format.h b/include/linux/media-bus-format.h
+index 0dfc11ee..c5fbda0e 100644
+--- a/include/linux/media-bus-format.h
++++ b/include/linux/media-bus-format.h
+@@ -112,7 +112,7 @@
+ #define MEDIA_BUS_FMT_YUV16_1X48 0x202a
+ #define MEDIA_BUS_FMT_UYYVYY16_0_5X48 0x202b
+
+-/* Bayer - next is 0x3021 */
++/* Bayer - next is 0x3022 */
+ #define MEDIA_BUS_FMT_SBGGR8_1X8 0x3001
+ #define MEDIA_BUS_FMT_SGBRG8_1X8 0x3013
+ #define MEDIA_BUS_FMT_SGRBG8_1X8 0x3002
+@@ -145,6 +145,8 @@
+ #define MEDIA_BUS_FMT_SGBRG16_1X16 0x301e
+ #define MEDIA_BUS_FMT_SGRBG16_1X16 0x301f
+ #define MEDIA_BUS_FMT_SRGGB16_1X16 0x3020
++/* Mixed bayer + ir pixel pattern found on ov01a1s */
++#define MEDIA_BUS_FMT_SIGIG_GBGR_IGIG_GRGB10_1X10 0x3021
+
+ /* JPEG compressed formats - next is 0x4002 */
+ #define MEDIA_BUS_FMT_JPEG_1X8 0x4001
+diff --git a/include/linux/videodev2.h b/include/linux/videodev2.h
+index bfb315d6..13c6c9d3 100644
+--- a/include/linux/videodev2.h
++++ b/include/linux/videodev2.h
+@@ -678,6 +678,9 @@ struct v4l2_pix_format {
+ #define V4L2_PIX_FMT_SGBRG16 v4l2_fourcc('G', 'B', '1', '6') /* 16 GBGB.. RGRG.. */
+ #define V4L2_PIX_FMT_SGRBG16 v4l2_fourcc('G', 'R', '1', '6') /* 16 GRGR.. BGBG.. */
+ #define V4L2_PIX_FMT_SRGGB16 v4l2_fourcc('R', 'G', '1', '6') /* 16 RGRG.. GBGB.. */
++ /* 10bit mixed bayer + ir pixel pattern found on ov01a1s */
++#define V4L2_PIX_FMT_SIGIG_GBGR_IGIG_GRGB10 v4l2_fourcc('O', 'V', '1', 'S') /* unpacked */
++#define V4L2_PIX_FMT_SIGIG_GBGR_IGIG_GRGB10P v4l2_fourcc('O', 'V', '1', 'P') /* packed */
+
+ /* HSV formats */
+ #define V4L2_PIX_FMT_HSV24 v4l2_fourcc('H', 'S', 'V', '3')
+diff --git a/src/libcamera/bayer_format.cpp b/src/libcamera/bayer_format.cpp
+index 3bf15fb4..ae227540 100644
+--- a/src/libcamera/bayer_format.cpp
++++ b/src/libcamera/bayer_format.cpp
+@@ -108,6 +108,8 @@ const std::map<BayerFormat, Formats, BayerFormatComparator> bayerToFormat{
+ { formats::SGRBG10, V4L2PixelFormat(V4L2_PIX_FMT_SGRBG10) } },
+ { { BayerFormat::RGGB, 10, BayerFormat::Packing::None },
+ { formats::SRGGB10, V4L2PixelFormat(V4L2_PIX_FMT_SRGGB10) } },
++ { { BayerFormat::IGIG_GBGR_IGIG_GRGB, 10, BayerFormat::Packing::None },
++ { formats::SIGIG_GBGR_IGIG_GRGB10, V4L2PixelFormat(V4L2_PIX_FMT_SIGIG_GBGR_IGIG_GRGB10) } },
+ { { BayerFormat::BGGR, 10, BayerFormat::Packing::CSI2 },
+ { formats::SBGGR10_CSI2P, V4L2PixelFormat(V4L2_PIX_FMT_SBGGR10P) } },
+ { { BayerFormat::GBRG, 10, BayerFormat::Packing::CSI2 },
+@@ -116,6 +118,8 @@ const std::map<BayerFormat, Formats, BayerFormatComparator> bayerToFormat{
+ { formats::SGRBG10_CSI2P, V4L2PixelFormat(V4L2_PIX_FMT_SGRBG10P) } },
+ { { BayerFormat::RGGB, 10, BayerFormat::Packing::CSI2 },
+ { formats::SRGGB10_CSI2P, V4L2PixelFormat(V4L2_PIX_FMT_SRGGB10P) } },
++ { { BayerFormat::IGIG_GBGR_IGIG_GRGB, 10, BayerFormat::Packing::CSI2 },
++ { formats::SIGIG_GBGR_IGIG_GRGB10_CSI2P, V4L2PixelFormat(V4L2_PIX_FMT_SIGIG_GBGR_IGIG_GRGB10P) } },
+ { { BayerFormat::BGGR, 10, BayerFormat::Packing::IPU3 },
+ { formats::SBGGR10_IPU3, V4L2PixelFormat(V4L2_PIX_FMT_IPU3_SBGGR10) } },
+ { { BayerFormat::GBRG, 10, BayerFormat::Packing::IPU3 },
+@@ -193,6 +197,7 @@ const std::unordered_map<unsigned int, BayerFormat> mbusCodeToBayer{
+ { MEDIA_BUS_FMT_SGBRG10_1X10, { BayerFormat::GBRG, 10, BayerFormat::Packing::None } },
+ { MEDIA_BUS_FMT_SGRBG10_1X10, { BayerFormat::GRBG, 10, BayerFormat::Packing::None } },
+ { MEDIA_BUS_FMT_SRGGB10_1X10, { BayerFormat::RGGB, 10, BayerFormat::Packing::None } },
++ { MEDIA_BUS_FMT_SIGIG_GBGR_IGIG_GRGB10_1X10, { BayerFormat::IGIG_GBGR_IGIG_GRGB, 10, BayerFormat::Packing::None } },
+ { MEDIA_BUS_FMT_SBGGR12_1X12, { BayerFormat::BGGR, 12, BayerFormat::Packing::None } },
+ { MEDIA_BUS_FMT_SGBRG12_1X12, { BayerFormat::GBRG, 12, BayerFormat::Packing::None } },
+ { MEDIA_BUS_FMT_SGRBG12_1X12, { BayerFormat::GRBG, 12, BayerFormat::Packing::None } },
+diff --git a/src/libcamera/camera_sensor.cpp b/src/libcamera/camera_sensor.cpp
+index 0ef78d9c..f19f72ea 100644
+--- a/src/libcamera/camera_sensor.cpp
++++ b/src/libcamera/camera_sensor.cpp
+@@ -510,6 +510,9 @@ int CameraSensor::initProperties()
+ case BayerFormat::MONO:
+ cfa = properties::draft::MONO;
+ break;
++ case BayerFormat::IGIG_GBGR_IGIG_GRGB:
++ cfa = properties::draft::RGB;
++ break;
+ }
+
+ properties_.set(properties::draft::ColorFilterArrangement, cfa);
+diff --git a/src/libcamera/formats.cpp b/src/libcamera/formats.cpp
+index 447e6238..aef7d598 100644
+--- a/src/libcamera/formats.cpp
++++ b/src/libcamera/formats.cpp
+@@ -599,6 +599,16 @@ const std::map<PixelFormat, PixelFormatInfo> pixelFormatInfo{
+ .pixelsPerGroup = 2,
+ .planes = {{ { 4, 1 }, { 0, 0 }, { 0, 0 } }},
+ } },
++ { formats::SIGIG_GBGR_IGIG_GRGB10, {
++ .name = "SIGIG_GBGR_IGIG_GRGB10",
++ .format = formats::SIGIG_GBGR_IGIG_GRGB10,
++ .v4l2Formats = { V4L2PixelFormat(V4L2_PIX_FMT_SIGIG_GBGR_IGIG_GRGB10), },
++ .bitsPerPixel = 10,
++ .colourEncoding = PixelFormatInfo::ColourEncodingRAW,
++ .packed = false,
++ .pixelsPerGroup = 4,
++ .planes = {{ { 4, 1 }, { 0, 0 }, { 0, 0 } }},
++ } },
+ { formats::SBGGR10_CSI2P, {
+ .name = "SBGGR10_CSI2P",
+ .format = formats::SBGGR10_CSI2P,
+@@ -639,6 +649,16 @@ const std::map<PixelFormat, PixelFormatInfo> pixelFormatInfo{
+ .pixelsPerGroup = 4,
+ .planes = {{ { 5, 1 }, { 0, 0 }, { 0, 0 } }},
+ } },
++ { formats::SIGIG_GBGR_IGIG_GRGB10_CSI2P, {
++ .name = "SIGIG_GBGR_IGIG_GRGB10_CSI2P",
++ .format = formats::SIGIG_GBGR_IGIG_GRGB10_CSI2P,
++ .v4l2Formats = { V4L2PixelFormat(V4L2_PIX_FMT_SIGIG_GBGR_IGIG_GRGB10P), },
++ .bitsPerPixel = 10,
++ .colourEncoding = PixelFormatInfo::ColourEncodingRAW,
++ .packed = true,
++ .pixelsPerGroup = 4,
++ .planes = {{ { 4, 1 }, { 0, 0 }, { 0, 0 } }},
++ } },
+ { formats::SBGGR12, {
+ .name = "SBGGR12",
+ .format = formats::SBGGR12,
+diff --git a/src/libcamera/formats.yaml b/src/libcamera/formats.yaml
+index 539ac0b3..0786a900 100644
+--- a/src/libcamera/formats.yaml
++++ b/src/libcamera/formats.yaml
+@@ -100,6 +100,8 @@ formats:
+ fourcc: DRM_FORMAT_SGBRG10
+ - SBGGR10:
+ fourcc: DRM_FORMAT_SBGGR10
++ - SIGIG_GBGR_IGIG_GRGB10:
++ fourcc: DRM_FORMAT_SIGIG_GBGR_IGIG_GRGB10
+
+ - SRGGB12:
+ fourcc: DRM_FORMAT_SRGGB12
+@@ -144,6 +146,9 @@ formats:
+ - SBGGR10_CSI2P:
+ fourcc: DRM_FORMAT_SBGGR10
+ mod: MIPI_FORMAT_MOD_CSI2_PACKED
++ - SIGIG_GBGR_IGIG_GRGB10_CSI2P:
++ fourcc: DRM_FORMAT_SIGIG_GBGR_IGIG_GRGB10
++ mod: MIPI_FORMAT_MOD_CSI2_PACKED
+
+ - SRGGB12_CSI2P:
+ fourcc: DRM_FORMAT_SRGGB12
+diff --git a/src/libcamera/v4l2_pixelformat.cpp b/src/libcamera/v4l2_pixelformat.cpp
+index 5551c62e..53078d99 100644
+--- a/src/libcamera/v4l2_pixelformat.cpp
++++ b/src/libcamera/v4l2_pixelformat.cpp
+@@ -153,6 +153,8 @@ const std::map<V4L2PixelFormat, V4L2PixelFormat::Info> vpf2pf{
+ { formats::SGRBG10, "10-bit Bayer GRGR/BGBG" } },
+ { V4L2PixelFormat(V4L2_PIX_FMT_SRGGB10),
+ { formats::SRGGB10, "10-bit Bayer RGRG/GBGB" } },
++ { V4L2PixelFormat(V4L2_PIX_FMT_SIGIG_GBGR_IGIG_GRGB10),
++ { formats::SIGIG_GBGR_IGIG_GRGB10, "10-bit Bayer GRGB/IGIG/GBGR/IGIG" } },
+ { V4L2PixelFormat(V4L2_PIX_FMT_SBGGR10P),
+ { formats::SBGGR10_CSI2P, "10-bit Bayer BGBG/GRGR Packed" } },
+ { V4L2PixelFormat(V4L2_PIX_FMT_SGBRG10P),
+@@ -161,6 +163,8 @@ const std::map<V4L2PixelFormat, V4L2PixelFormat::Info> vpf2pf{
+ { formats::SGRBG10_CSI2P, "10-bit Bayer GRGR/BGBG Packed" } },
+ { V4L2PixelFormat(V4L2_PIX_FMT_SRGGB10P),
+ { formats::SRGGB10_CSI2P, "10-bit Bayer RGRG/GBGB Packed" } },
++ { V4L2PixelFormat(V4L2_PIX_FMT_SIGIG_GBGR_IGIG_GRGB10P),
++ { formats::SIGIG_GBGR_IGIG_GRGB10_CSI2P, "10-bit Bayer GRGB/IGIG/GBGR/IGIG Packed" } },
+ { V4L2PixelFormat(V4L2_PIX_FMT_SBGGR12),
+ { formats::SBGGR12, "12-bit Bayer BGBG/GRGR" } },
+ { V4L2PixelFormat(V4L2_PIX_FMT_SGBRG12),
+diff --git a/src/libcamera/v4l2_subdevice.cpp b/src/libcamera/v4l2_subdevice.cpp
+index 15e8206a..4ad37aaf 100644
+--- a/src/libcamera/v4l2_subdevice.cpp
++++ b/src/libcamera/v4l2_subdevice.cpp
+@@ -128,6 +128,7 @@ const std::map<uint32_t, V4L2SubdeviceFormatInfo> formatInfoMap = {
+ { MEDIA_BUS_FMT_SGBRG10_1X10, { 10, "SGBRG10_1X10", PixelFormatInfo::ColourEncodingRAW } },
+ { MEDIA_BUS_FMT_SGRBG10_1X10, { 10, "SGRBG10_1X10", PixelFormatInfo::ColourEncodingRAW } },
+ { MEDIA_BUS_FMT_SRGGB10_1X10, { 10, "SRGGB10_1X10", PixelFormatInfo::ColourEncodingRAW } },
++ { MEDIA_BUS_FMT_SIGIG_GBGR_IGIG_GRGB10_1X10, { 10, "SIGIG_GBGR_IGIG_GRGB10_1X10", PixelFormatInfo::ColourEncodingRAW } },
+ { MEDIA_BUS_FMT_SBGGR12_1X12, { 12, "SBGGR12_1X12", PixelFormatInfo::ColourEncodingRAW } },
+ { MEDIA_BUS_FMT_SGBRG12_1X12, { 12, "SGBRG12_1X12", PixelFormatInfo::ColourEncodingRAW } },
+ { MEDIA_BUS_FMT_SGRBG12_1X12, { 12, "SGRBG12_1X12", PixelFormatInfo::ColourEncodingRAW } },
+--
+2.43.0
+
diff --git a/nix/pkgs/libcamera/0021-libcamera-swstats_cpu-Add-support-for-10bpp-IGIG_GBG.patch b/nix/pkgs/libcamera/0021-libcamera-swstats_cpu-Add-support-for-10bpp-IGIG_GBG.patch
new file mode 100644
index 0000000..44985a9
--- /dev/null
+++ b/nix/pkgs/libcamera/0021-libcamera-swstats_cpu-Add-support-for-10bpp-IGIG_GBG.patch
@@ -0,0 +1,131 @@
+From f939e68a3ef556e572f0140df6d7ef17d72f457e Mon Sep 17 00:00:00 2001
+From: Marttico <g.martti@gmail.com>
+Date: Wed, 20 Dec 2023 20:26:15 +0100
+Subject: [PATCH 21/25] libcamera: swstats_cpu: Add support for 10bpp
+ IGIG_GBGR_IGIG_GRGB input
+
+Add support to SwStatsCpu for 10bpp IGIG_GBGR_IGIG_GRGB input
+generated by the Omnivision ov01a1s sensor.
+
+Co-authored-by: Dennis Bonke <admin@dennisbonke.com>
+Signed-off-by: Dennis Bonke <admin@dennisbonke.com>
+Co-authored-by: Toon Langendam <t.langendam@gmail.com>
+Signed-off-by: Toon Langendam <t.langendam@gmail.com>
+Signed-off-by: Marttico <g.martti@gmail.com>
+Signed-off-by: Hans de Goede <hdegoede@redhat.com>
+---
+ .../internal/software_isp/swstats_cpu.h | 3 +
+ src/libcamera/software_isp/swstats_cpu.cpp | 76 +++++++++++++++++++
+ 2 files changed, 79 insertions(+)
+
+diff --git a/include/libcamera/internal/software_isp/swstats_cpu.h b/include/libcamera/internal/software_isp/swstats_cpu.h
+index e7abc6bb..a47241e1 100644
+--- a/include/libcamera/internal/software_isp/swstats_cpu.h
++++ b/include/libcamera/internal/software_isp/swstats_cpu.h
+@@ -42,6 +42,9 @@ private:
+ /* Bayer 10 bpp packed */
+ void statsBGGR10PLine0(const uint8_t *src[]);
+ void statsGBRG10PLine0(const uint8_t *src[]);
++ /* IGIG_GBGR_IGIG_GRGB 10 bpp unpacked */
++ void statsRGBIR10Line0(const uint8_t *src[]);
++ void statsRGBIR10Line2(const uint8_t *src[]);
+ void resetStats(void);
+ void finishStats(void);
+
+diff --git a/src/libcamera/software_isp/swstats_cpu.cpp b/src/libcamera/software_isp/swstats_cpu.cpp
+index 87550371..96e21be5 100644
+--- a/src/libcamera/software_isp/swstats_cpu.cpp
++++ b/src/libcamera/software_isp/swstats_cpu.cpp
+@@ -187,6 +187,68 @@ void SwStatsCpu::statsGBRG10PLine0(const uint8_t *src[])
+ statsBayer10P(window_.width, src0, src1, false, stats_);
+ }
+
++void SwStatsCpu::statsRGBIR10Line0(const uint8_t *src[])
++{
++ const uint16_t *src0_16 = (const uint16_t *)src[2] + window_.x;
++ const uint16_t *src1_16 = (const uint16_t *)src[3] + window_.x;
++ uint16_t g3, g4;
++
++ SWISP_LINARO_START_LINE_STATS(uint16_t)
++
++ /* x += 8 sample every other 4x4 block */
++ for (int x = 0; x < (int)window_.width; x += 8) {
++ /* IGIG */
++ //i = src0_16[x];
++ g2 = src0_16[x + 1];
++ //i = src0_16[x + 2];
++ g4 = src0_16[x + 3];
++
++ /* GBGR */
++ g = src1_16[x];
++ b = src1_16[x + 1];
++ g3 = src1_16[x + 2];
++ r = src1_16[x + 3];
++
++ g = (g + g2 + g3 + g4) / 4;
++
++ /* divide Y by 4 for 10 -> 8 bpp value */
++ SWISP_LINARO_ACCUMULATE_LINE_STATS(4)
++ }
++
++ SWISP_LINARO_FINISH_LINE_STATS()
++}
++
++void SwStatsCpu::statsRGBIR10Line2(const uint8_t *src[])
++{
++ const uint16_t *src0_16 = (const uint16_t *)src[2] + window_.x;
++ const uint16_t *src1_16 = (const uint16_t *)src[3] + window_.x;
++ uint16_t g3, g4;
++
++ SWISP_LINARO_START_LINE_STATS(uint16_t)
++
++ /* x += 8 sample every other 4x4 block */
++ for (int x = 0; x < (int)window_.width; x += 8) {
++ /* IGIG */
++ //i = src0_16[x];
++ g2 = src0_16[x + 1];
++ //i = src0_16[x + 2];
++ g4 = src0_16[x + 3];
++
++ /* GRGB */
++ g = src1_16[x];
++ r = src1_16[x + 1];
++ g3 = src1_16[x + 2];
++ b = src1_16[x + 3];
++
++ g = (g + g2 + g3 + g4) / 4;
++
++ /* divide Y by 4 for 10 -> 8 bpp value */
++ SWISP_LINARO_ACCUMULATE_LINE_STATS(4)
++ }
++
++ SWISP_LINARO_FINISH_LINE_STATS()
++}
++
+ void SwStatsCpu::resetStats(void)
+ {
+ stats_.sumR_ = 0;
+@@ -282,6 +344,20 @@ int SwStatsCpu::configure(const StreamConfiguration &inputCfg)
+ }
+ }
+
++ if (bayerFormat.bitDepth == 10 &&
++ bayerFormat.packing == BayerFormat::Packing::None &&
++ bayerFormat.order == BayerFormat::IGIG_GBGR_IGIG_GRGB) {
++ bpp_ = 16;
++ patternSize_.height = 4;
++ patternSize_.width = 4;
++ y_skip_mask_ = 0x04;
++ x_shift_ = 0;
++ swap_lines_ = false;
++ stats0_ = (SwStats::statsProcessFn)&SwStatsCpu::statsRGBIR10Line0;
++ stats2_ = (SwStats::statsProcessFn)&SwStatsCpu::statsRGBIR10Line2;
++ return 0;
++ }
++
+ LOG(SwStats, Info)
+ << "Unsupported input format " << inputCfg.pixelFormat.toString();
+ return -EINVAL;
+--
+2.43.0
+
diff --git a/nix/pkgs/libcamera/0022-libcamera-debayer_cpu-Add-support-for-10bpp-IGIG_GBG.patch b/nix/pkgs/libcamera/0022-libcamera-debayer_cpu-Add-support-for-10bpp-IGIG_GBG.patch
new file mode 100644
index 0000000..4054a95
--- /dev/null
+++ b/nix/pkgs/libcamera/0022-libcamera-debayer_cpu-Add-support-for-10bpp-IGIG_GBG.patch
@@ -0,0 +1,315 @@
+From e3638943a8bd3f93b8d81c3996035c60755b97f6 Mon Sep 17 00:00:00 2001
+From: Marttico <g.martti@gmail.com>
+Date: Wed, 20 Dec 2023 20:28:12 +0100
+Subject: [PATCH 22/25] libcamera: debayer_cpu: Add support for 10bpp
+ IGIG_GBGR_IGIG_GRGB input
+
+Add support to DebayerCpu for 10bpp IGIG_GBGR_IGIG_GRGB input
+generated by the Omnivision ov01a1s sensor.
+
+Co-authored-by: Dennis Bonke <admin@dennisbonke.com>
+Signed-off-by: Dennis Bonke <admin@dennisbonke.com>
+Co-authored-by: Toon Langendam <t.langendam@gmail.com>
+Signed-off-by: Toon Langendam <t.langendam@gmail.com>
+Signed-off-by: Marttico <g.martti@gmail.com>
+Signed-off-by: Hans de Goede <hdegoede@redhat.com>
+---
+ .../internal/software_isp/debayer_cpu.h | 5 +
+ src/libcamera/software_isp/debayer_cpu.cpp | 251 ++++++++++++++++++
+ 2 files changed, 256 insertions(+)
+
+diff --git a/include/libcamera/internal/software_isp/debayer_cpu.h b/include/libcamera/internal/software_isp/debayer_cpu.h
+index bdeab7c0..52af117f 100644
+--- a/include/libcamera/internal/software_isp/debayer_cpu.h
++++ b/include/libcamera/internal/software_isp/debayer_cpu.h
+@@ -96,6 +96,11 @@ private:
+ void debayer10P_GRGR_BGR888(uint8_t *dst, const uint8_t *src[]);
+ void debayer10P_GBGB_BGR888(uint8_t *dst, const uint8_t *src[]);
+ void debayer10P_RGRG_BGR888(uint8_t *dst, const uint8_t *src[]);
++ /* IGIG_GBGR_IGIG_GRGB unpacked 10-bit raw bayer format */
++ void debayerIGIG10Line0(uint8_t *dst, const uint8_t *src[]);
++ void debayerGBGR10Line1(uint8_t *dst, const uint8_t *src[]);
++ void debayerIGIG10Line2(uint8_t *dst, const uint8_t *src[]);
++ void debayerGRGB10Line3(uint8_t *dst, const uint8_t *src[]);
+
+ typedef void (DebayerCpu::*debayerFn)(uint8_t *dst, const uint8_t *src[]);
+
+diff --git a/src/libcamera/software_isp/debayer_cpu.cpp b/src/libcamera/software_isp/debayer_cpu.cpp
+index 0edea4d3..41c8805f 100644
+--- a/src/libcamera/software_isp/debayer_cpu.cpp
++++ b/src/libcamera/software_isp/debayer_cpu.cpp
+@@ -228,6 +228,238 @@ void DebayerCpu::debayer10P_RGRG_BGR888(uint8_t *dst, const uint8_t *src[])
+ }
+ }
+
++void DebayerCpu::debayerIGIG10Line0(uint8_t *dst, const uint8_t *src[])
++{
++ const uint16_t *prev = (const uint16_t *)src[1];
++ const uint16_t *curr = (const uint16_t *)src[2];
++ const uint16_t *next = (const uint16_t *)src[3];
++
++ for (int x = 0; x < (int)window_.width;) {
++ /*
++ * IGIG line pixel 0: IGIGI
++ * GBGRG
++ * IGIGI
++ * GRGBG
++ * IGIGI
++ */
++ *dst++ = blue_[(prev[x - 1] + next[x + 1]) / 8];
++ *dst++ = green_[(curr[x - 1] + curr[x + 1] + prev[x] + next[x]) / 16];
++ *dst++ = red_[(prev[x + 1] + next[x - 1]) / 8];
++ x++;
++
++ /*
++ * IGIG line pixel 1: GIGIG
++ * BGRGB
++ * GIGIG
++ * RGBGR
++ * GIGIG
++ */
++ *dst++ = blue_[next[x] / 4];
++ *dst++ = green_[curr[x] / 4];
++ *dst++ = red_[prev[x] / 4];
++ x++;
++
++ /*
++ * IGIG line pixel 2: IGIGI
++ * GRGBG
++ * IGIGI
++ * GBGRG
++ * IGIGI
++ */
++ *dst++ = blue_[(prev[x + 1] + next[x - 1]) / 8];
++ *dst++ = green_[(curr[x - 1] + curr[x + 1] + prev[x] + next[x]) / 16];
++ *dst++ = red_[(prev[x - 1] + next[x + 1]) / 8];
++ x++;
++
++ /*
++ * IGIG line pixel 3: GIGIG
++ * RGBGR
++ * GIGIG
++ * BGRGB
++ * GIGIG
++ */
++ *dst++ = blue_[prev[x] / 4];
++ *dst++ = green_[curr[x] / 4];
++ *dst++ = red_[next[x] / 4];
++ x++;
++ }
++}
++
++void DebayerCpu::debayerGBGR10Line1(uint8_t *dst, const uint8_t *src[])
++{
++ const uint16_t *prev2 = (const uint16_t *)src[0];
++ const uint16_t *prev = (const uint16_t *)src[1];
++ const uint16_t *curr = (const uint16_t *)src[2];
++ const uint16_t *next = (const uint16_t *)src[3];
++ const uint16_t *next2 = (const uint16_t *)src[4];
++
++ for (int x = 0; x < (int)window_.width;) {
++ /*
++ * GBGR line pixel 0: GBGRG
++ * IGIGI
++ * GRGBG
++ * IGIGI
++ * GBGRG
++ */
++ *dst++ = blue_[curr[x + 1] / 4];
++ *dst++ = green_[curr[x] / 4];
++ *dst++ = red_[curr[x - 1] / 4];
++ x++;
++
++ /*
++ * GBGR line pixel 1: BGRGB
++ * GIGIG
++ * RGBGR
++ * GIGIG
++ * BGRGB
++ */
++ *dst++ = blue_[curr[x] / 4];
++ *dst++ = green_[(curr[x - 1] + curr[x + 1] + prev[x] + next[x]) / 16];
++ *dst++ = red_[(curr[x - 2] + curr[x + 2] + prev2[x] + next2[x]) / 16];
++ x++;
++
++ /*
++ * GBGR line pixel 2: GRGBG
++ * IGIGI
++ * GBGRG
++ * IGIGI
++ * GRGBG
++ */
++ *dst++ = blue_[curr[x - 1] / 4];
++ *dst++ = green_[curr[x] / 4];
++ *dst++ = red_[curr[x + 1] / 4];
++ x++;
++
++ /*
++ * GBGR line pixel 3: RGBGR
++ * GIGIG
++ * BGRGB
++ * GIGIG
++ * RGBGR
++ */
++ *dst++ = blue_[(curr[x - 2] + curr[x + 2] + prev2[x] + next2[x]) / 16];
++ *dst++ = green_[(curr[x - 1] + curr[x + 1] + prev[x] + next[x]) / 16];
++ *dst++ = red_[curr[x] / 4];
++ x++;
++ }
++}
++
++void DebayerCpu::debayerIGIG10Line2(uint8_t *dst, const uint8_t *src[])
++{
++ const uint16_t *prev = (const uint16_t *)src[1];
++ const uint16_t *curr = (const uint16_t *)src[2];
++ const uint16_t *next = (const uint16_t *)src[3];
++
++ for (int x = 0; x < (int)window_.width;) {
++ /*
++ * IGIG line pixel 0: IGIGI
++ * GRGBG
++ * IGIGI
++ * GBGRG
++ * IGIGI
++ */
++ *dst++ = blue_[(prev[x + 1] + next[x - 1]) / 8];
++ *dst++ = green_[(curr[x - 1] + curr[x + 1] + prev[x] + next[x]) / 16];
++ *dst++ = red_[(prev[x - 1] + next[x + 1]) / 8];
++ x++;
++
++ /*
++ * IGIG line pixel 1: GIGIG
++ * RGBGR
++ * GIGIG
++ * BGRGB
++ * GIGIG
++ */
++ *dst++ = blue_[prev[x] / 4];
++ *dst++ = green_[curr[x] / 4];
++ *dst++ = red_[next[x] / 4];
++ x++;
++
++ /*
++ * IGIG line pixel 2: IGIGI
++ * GBGRG
++ * IGIGI
++ * GRGBG
++ * IGIGI
++ */
++ *dst++ = blue_[(prev[x - 1] + next[x + 1]) / 8];
++ *dst++ = green_[(curr[x - 1] + curr[x + 1] + prev[x] + next[x]) / 16];
++ *dst++ = red_[(prev[x + 1] + next[x - 1]) / 8];
++ x++;
++
++ /*
++ * IGIG line pixel 3: GIGIG
++ * BGRGB
++ * GIGIG
++ * RGBGR
++ * GIGIG
++ */
++ *dst++ = blue_[next[x] / 4];
++ *dst++ = green_[curr[x] / 4];
++ *dst++ = red_[prev[x] / 4];
++ x++;
++ }
++}
++
++void DebayerCpu::debayerGRGB10Line3(uint8_t *dst, const uint8_t *src[])
++{
++ const uint16_t *prev2 = (const uint16_t *)src[0];
++ const uint16_t *prev = (const uint16_t *)src[1];
++ const uint16_t *curr = (const uint16_t *)src[2];
++ const uint16_t *next = (const uint16_t *)src[3];
++ const uint16_t *next2 = (const uint16_t *)src[4];
++
++ for (int x = 0; x < (int)window_.width;) {
++ /*
++ * GRGB line pixel 0: GRGBG
++ * IGIGI
++ * GBGRG
++ * IGIGI
++ * GRGBG
++ */
++ *dst++ = blue_[curr[x - 1] / 4];
++ *dst++ = green_[curr[x] / 4];
++ *dst++ = red_[curr[x + 1] / 4];
++ x++;
++
++ /*
++ * GRGB line pixel 1: RGBGR
++ * GIGIG
++ * BGRGB
++ * GIGIG
++ * RGBGR
++ */
++ *dst++ = blue_[(curr[x - 2] + curr[x + 2] + prev2[x] + next2[x]) / 16];
++ *dst++ = green_[(curr[x - 1] + curr[x + 1] + prev[x] + next[x]) / 16];
++ *dst++ = red_[curr[x] / 4];
++ x++;
++
++ /*
++ * GRGB line pixel 2: GBGRG
++ * IGIGI
++ * GRGBG
++ * IGIGI
++ * GBGRG
++ */
++ *dst++ = blue_[curr[x + 1] / 4];
++ *dst++ = green_[curr[x] / 4];
++ *dst++ = red_[curr[x - 1] / 4];
++ x++;
++
++ /*
++ * GRGB line pixel 3: BGRGB
++ * GIGIG
++ * RGBGR
++ * GIGIG
++ * BGRGB
++ */
++ *dst++ = blue_[curr[x] / 4];
++ *dst++ = green_[(curr[x - 1] + curr[x + 1] + prev[x] + next[x]) / 16];
++ *dst++ = red_[(curr[x - 2] + curr[x + 2] + prev2[x] + next2[x]) / 16];
++ x++;
++ }
++}
++
+ static bool isStandardBayerOrder(BayerFormat::Order order)
+ {
+ return order == BayerFormat::BGGR || order == BayerFormat::GBRG ||
+@@ -259,6 +491,15 @@ int DebayerCpu::getInputConfig(PixelFormat inputFormat, DebayerInputConfig &conf
+ return 0;
+ }
+
++ if (bayerFormat.bitDepth == 10 && bayerFormat.packing == BayerFormat::Packing::None &&
++ bayerFormat.order == BayerFormat::IGIG_GBGR_IGIG_GRGB) {
++ config.bpp = 16;
++ config.patternSize.height = 4;
++ config.patternSize.width = 4;
++ config.outputFormats = std::vector<PixelFormat>({ formats::RGB888 });
++ return 0;
++ }
++
+ LOG(Debayer, Info)
+ << "Unsupported input format " << inputFormat.toString();
+ return -EINVAL;
+@@ -384,6 +625,16 @@ int DebayerCpu::setDebayerFunctions(PixelFormat inputFormat, PixelFormat outputF
+ }
+ }
+
++ if (bayerFormat.bitDepth == 10 &&
++ bayerFormat.packing == BayerFormat::Packing::None &&
++ bayerFormat.order == BayerFormat::IGIG_GBGR_IGIG_GRGB) {
++ debayer0_ = &DebayerCpu::debayerIGIG10Line0;
++ debayer1_ = &DebayerCpu::debayerGBGR10Line1;
++ debayer2_ = &DebayerCpu::debayerIGIG10Line2;
++ debayer3_ = &DebayerCpu::debayerGRGB10Line3;
++ return 0;
++ }
++
+ invalid_fmt:
+ LOG(Debayer, Error) << "Unsupported input output format combination";
+ return -EINVAL;
+--
+2.43.0
+
diff --git a/nix/pkgs/libcamera/0023-libcamera-Add-Software-ISP-benchmarking-documentatio.patch b/nix/pkgs/libcamera/0023-libcamera-Add-Software-ISP-benchmarking-documentatio.patch
new file mode 100644
index 0000000..ec49917
--- /dev/null
+++ b/nix/pkgs/libcamera/0023-libcamera-Add-Software-ISP-benchmarking-documentatio.patch
@@ -0,0 +1,130 @@
+From 26e96232c314f9d34f6ee3be365c04918967084e Mon Sep 17 00:00:00 2001
+From: Hans de Goede <hdegoede@redhat.com>
+Date: Mon, 22 Jan 2024 17:18:00 +0100
+Subject: [PATCH 23/25] libcamera: Add "Software ISP benchmarking"
+ documentation
+
+Add a "Software ISP benchmarking" documentation section which describes
+the performance/power consumption measurements used during
+the Software ISP's development.
+
+Signed-off-by: Hans de Goede <hdegoede@redhat.com>
+---
+ Documentation/index.rst | 1 +
+ Documentation/meson.build | 1 +
+ Documentation/software-isp-benchmarking.rst | 82 +++++++++++++++++++++
+ 3 files changed, 84 insertions(+)
+ create mode 100644 Documentation/software-isp-benchmarking.rst
+
+diff --git a/Documentation/index.rst b/Documentation/index.rst
+index 63fac72d..5442ae75 100644
+--- a/Documentation/index.rst
++++ b/Documentation/index.rst
+@@ -24,3 +24,4 @@
+ Lens driver requirements <lens_driver_requirements>
+ Python Bindings <python-bindings>
+ Camera Sensor Model <camera-sensor-model>
++ SoftwareISP Benchmarking <software-isp-benchmarking>
+diff --git a/Documentation/meson.build b/Documentation/meson.build
+index 7a58fec8..3872e0a8 100644
+--- a/Documentation/meson.build
++++ b/Documentation/meson.build
+@@ -80,6 +80,7 @@ if sphinx.found()
+ 'lens_driver_requirements.rst',
+ 'python-bindings.rst',
+ 'sensor_driver_requirements.rst',
++ 'software-isp-benchmarking.rst',
+ '../README.rst',
+ ]
+
+diff --git a/Documentation/software-isp-benchmarking.rst b/Documentation/software-isp-benchmarking.rst
+new file mode 100644
+index 00000000..738c8c65
+--- /dev/null
++++ b/Documentation/software-isp-benchmarking.rst
+@@ -0,0 +1,82 @@
++.. SPDX-License-Identifier: CC-BY-SA-4.0
++
++.. _software-isp-benchmarking:
++
++Software ISP benchmarking
++=========================
++
++The Software ISP is paricular sensitive to performance regressions
++therefor it is a good idea to always benchmark the Software ISP
++before and after making changes to it and ensure that there are
++no performance regressions.
++
++DebayerCpu class builtin benchmark
++----------------------------------
++
++The DebayerCpu class has a builtin benchmark. This benchmark
++measures the time spend on processing (collecting statistics
++and debayering) only, it does not measure the time spend on
++capturing or outputting the frames.
++
++The builtin benchmark always runs. So this can be used by simply
++running "cam" or "qcam" with a pipeline using the Software ISP.
++
++When it runs it will skip measuring the first 30 frames to
++allow the caches and the CPU temperature (turbo-ing) to warm-up
++and then it measures 30 fps and shows the total and per frame
++processing time using an info level log message:
++
++.. code-block::
++
++ INFO Debayer debayer_cpu.cpp:907 Processed 30 frames in 244317us, 8143 us/frame
++
++To get stable measurements it is advised to disable any other processes which
++may cause significant CPU usage (e.g. disable wifi, bluetooth and browsers).
++When possible it is also advisable to disable CPU turbo-ing and
++frequency-scaling.
++
++For example when benchmarking on a Lenovo ThinkPad X1 Yoga Gen 8, with
++the charger plugged in, the CPU can be fixed to run at 2 GHz using:
++
++.. code-block::
++
++ sudo x86_energy_perf_policy --turbo-enable 0
++ sudo cpupower frequency-set -d 2GHz -u 2GHz
++
++with these settings the builtin bench reports a processing time of ~7.8ms/frame
++on this laptop for FHD SGRBG10 (unpacked) bayer data.
++
++Measuring power consumption
++---------------------------
++
++Since the Software ISP is often used on mobile devices it is also
++important to measure power consumption and ensure that that does
++not regress.
++
++For example to measure power consumption on a Lenovo ThinkPad X1 Yoga Gen 8
++it needs to be running on battery and it should be configured with its
++platform-profile (/sys/firmware/acpi/platform_profile) set to balanced and
++with its default turbo and frequency-scaling behavior to match real world usage.
++
++Then start qcam to capture a FHD picture at 30 fps and position the qcam window
++so that it is fully visible. After this run the following command to monitor
++the power consumption:
++
++.. code-block::
++
++ watch -n 10 cat /sys/class/power_supply/BAT0/power_now /sys/class/hwmon/hwmon6/fan?_input
++
++Note this not only measures the power consumption in ųW it also monitors
++the speed of this laptop's 2 fans. This is important because depending on
++the ambient temperature the 2 fans may spin up while testing and this
++will cause an additional power consumption of approx. 0.5W messing up
++the measurement.
++
++After starting qcam + the watch command let the laptop sit without using
++it for 2 minutes for the readings to stabilize. Then check that the fans
++have not turned on and manually take a couple of consecutive power readings
++and avarage these.
++
++On the example Lenovo ThinkPad X1 Yoga Gen 8 laptop this results in
++a measured power consumption of approx. 13W while running qcam versus
++approx. 4-5W while setting idle with its OLED panel on.
+--
+2.43.0
+
diff --git a/nix/pkgs/libcamera/0024-ov01a1s-HACK.patch b/nix/pkgs/libcamera/0024-ov01a1s-HACK.patch
new file mode 100644
index 0000000..3b558e0
--- /dev/null
+++ b/nix/pkgs/libcamera/0024-ov01a1s-HACK.patch
@@ -0,0 +1,95 @@
+From 9bec33e5c7e6765734eeef2d22d7f7f65dee2264 Mon Sep 17 00:00:00 2001
+From: Hans de Goede <hdegoede@redhat.com>
+Date: Tue, 19 Dec 2023 15:45:51 +0100
+Subject: [PATCH 24/25] ov01a1s HACK
+
+Signed-off-by: Hans de Goede <hdegoede@redhat.com>
+---
+ src/libcamera/camera_sensor.cpp | 6 ++++++
+ src/libcamera/software_isp/debayer_cpu.cpp | 8 ++++++++
+ src/libcamera/software_isp/swstats_cpu.cpp | 5 +++++
+ 3 files changed, 19 insertions(+)
+
+diff --git a/src/libcamera/camera_sensor.cpp b/src/libcamera/camera_sensor.cpp
+index f19f72ea..7ad4b9ef 100644
+--- a/src/libcamera/camera_sensor.cpp
++++ b/src/libcamera/camera_sensor.cpp
+@@ -34,6 +34,9 @@
+
+ namespace libcamera {
+
++// HACK HACK
++bool is_ov01a1s = false;
++
+ LOG_DEFINE_CATEGORY(CameraSensor)
+
+ /**
+@@ -426,6 +429,9 @@ int CameraSensor::initProperties()
+ model_ = subdev_->model();
+ properties_.set(properties::Model, utils::toAscii(model_));
+
++ if (model_ == "ov01a1s")
++ is_ov01a1s = true;
++
+ /* Generate a unique ID for the sensor. */
+ int ret = generateId();
+ if (ret)
+diff --git a/src/libcamera/software_isp/debayer_cpu.cpp b/src/libcamera/software_isp/debayer_cpu.cpp
+index 41c8805f..b6393925 100644
+--- a/src/libcamera/software_isp/debayer_cpu.cpp
++++ b/src/libcamera/software_isp/debayer_cpu.cpp
+@@ -23,6 +23,8 @@
+
+ namespace libcamera {
+
++extern bool is_ov01a1s;
++
+ DebayerCpu::DebayerCpu(std::unique_ptr<SwStatsCpu> stats)
+ : stats_(std::move(stats)), gamma_correction_(1.0)
+ {
+@@ -471,6 +473,9 @@ int DebayerCpu::getInputConfig(PixelFormat inputFormat, DebayerInputConfig &conf
+ BayerFormat bayerFormat =
+ BayerFormat::fromPixelFormat(inputFormat);
+
++ if (is_ov01a1s)
++ bayerFormat.order = BayerFormat::IGIG_GBGR_IGIG_GRGB;
++
+ if ((bayerFormat.bitDepth == 8 || bayerFormat.bitDepth == 10 || bayerFormat.bitDepth == 12) &&
+ bayerFormat.packing == BayerFormat::Packing::None &&
+ isStandardBayerOrder(bayerFormat.order)) {
+@@ -548,6 +553,9 @@ int DebayerCpu::setDebayerFunctions(PixelFormat inputFormat, PixelFormat outputF
+ BayerFormat bayerFormat =
+ BayerFormat::fromPixelFormat(inputFormat);
+
++ if (is_ov01a1s)
++ bayerFormat.order = BayerFormat::IGIG_GBGR_IGIG_GRGB;
++
+ swapRedBlueGains_ = false;
+
+ switch (outputFormat) {
+diff --git a/src/libcamera/software_isp/swstats_cpu.cpp b/src/libcamera/software_isp/swstats_cpu.cpp
+index 96e21be5..503ce799 100644
+--- a/src/libcamera/software_isp/swstats_cpu.cpp
++++ b/src/libcamera/software_isp/swstats_cpu.cpp
+@@ -19,6 +19,8 @@
+
+ namespace libcamera {
+
++extern bool is_ov01a1s;
++
+ SwStatsCpu::SwStatsCpu()
+ : SwStats()
+ {
+@@ -301,6 +303,9 @@ int SwStatsCpu::configure(const StreamConfiguration &inputCfg)
+ BayerFormat bayerFormat =
+ BayerFormat::fromPixelFormat(inputCfg.pixelFormat);
+
++ if (is_ov01a1s)
++ bayerFormat.order = BayerFormat::IGIG_GBGR_IGIG_GRGB;
++
+ startFrame_ = (SwStats::statsVoidFn)&SwStatsCpu::resetStats;
+ finishFrame_ = (SwStats::statsVoidFn)&SwStatsCpu::finishStats;
+
+--
+2.43.0
+
diff --git a/nix/pkgs/libcamera/0025-libcamera-debayer_cpu-Make-the-minimum-size-1280x720.patch b/nix/pkgs/libcamera/0025-libcamera-debayer_cpu-Make-the-minimum-size-1280x720.patch
new file mode 100644
index 0000000..1f26735
--- /dev/null
+++ b/nix/pkgs/libcamera/0025-libcamera-debayer_cpu-Make-the-minimum-size-1280x720.patch
@@ -0,0 +1,40 @@
+From 4f2c94ba8b7f9f4d85a1d7e03f4c5272d92c3361 Mon Sep 17 00:00:00 2001
+From: Hans de Goede <hdegoede@redhat.com>
+Date: Wed, 24 Jan 2024 20:44:29 +0100
+Subject: [PATCH 25/25] libcamera: debayer_cpu: Make the minimum size 1280x720
+
+pipewire + firefox default to what looks like 640x480 if we export
+the entire supported cropping range. Hardcode 720p as minsize for now.
+
+Signed-off-by: Hans de Goede <hdegoede@redhat.com>
+---
+ include/libcamera/internal/software_isp/debayer.h | 13 ++++++++++---
+ 1 file changed, 10 insertions(+), 3 deletions(-)
+
+diff --git a/include/libcamera/internal/software_isp/debayer.h b/include/libcamera/internal/software_isp/debayer.h
+index 39e6f393..4348173d 100644
+--- a/include/libcamera/internal/software_isp/debayer.h
++++ b/include/libcamera/internal/software_isp/debayer.h
+@@ -112,9 +112,16 @@ public:
+ return {};
+ }
+
+- return SizeRange(Size(pattern_size.width, pattern_size.height),
+- Size((inputSize.width - 2 * pattern_size.width) & ~(pattern_size.width - 1),
+- (inputSize.height - 2 * pattern_size.height) & ~(pattern_size.height - 1)),
++ /*
++ * pipewire + firefox default to what looks like 640x480
++ * if we export the entire supported cropping range.
++ * Hardcode 720p as minsize for now. Minsize should be
++ * Size(pattern_size.width, pattern_size.height)
++ */
++ unsigned int w = (inputSize.width - 2 * pattern_size.width) & ~(pattern_size.width - 1);
++ unsigned int h = (inputSize.height - 2 * pattern_size.height) & ~(pattern_size.height - 1);
++ return SizeRange(Size(std::min(w, 1280u), std::min(h, 720u)),
++ Size(w, h),
+ pattern_size.width, pattern_size.height);
+ }
+
+--
+2.43.0
+
--
2.43.0
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment