about summary refs log tree commit diff
path: root/users/flokli
diff options
context:
space:
mode:
Diffstat (limited to 'users/flokli')
-rw-r--r--users/flokli/OWNERS6
-rw-r--r--users/flokli/archeology/OWNERS1
-rw-r--r--users/flokli/archeology/README.md5
-rw-r--r--users/flokli/archeology/default.nix51
-rw-r--r--users/flokli/archeology/parse_bucket_logs.rs42
-rw-r--r--users/flokli/archivist/OWNERS1
-rw-r--r--users/flokli/archivist/default.nix28
-rw-r--r--users/flokli/ipu6-softisp/README.md29
-rw-r--r--users/flokli/ipu6-softisp/config.nix74
-rw-r--r--users/flokli/ipu6-softisp/default.nix57
-rw-r--r--users/flokli/ipu6-softisp/kernel/.skip-tree0
-rw-r--r--users/flokli/ipu6-softisp/kernel/softisp.patch18077
-rw-r--r--users/flokli/ipu6-softisp/libcamera/.skip-tree0
-rw-r--r--users/flokli/ipu6-softisp/libcamera/0001-libcamera-pipeline-simple-fix-size-adjustment-in-val.patch82
-rw-r--r--users/flokli/ipu6-softisp/libcamera/0002-libcamera-internal-Move-dma_heaps.-h-cpp-to-common-d.patch350
-rw-r--r--users/flokli/ipu6-softisp/libcamera/0003-libcamera-dma_heaps-extend-DmaHeap-class-to-support-.patch169
-rw-r--r--users/flokli/ipu6-softisp/libcamera/0004-libcamera-internal-Move-SharedMemObject-class-to-a-c.patch69
-rw-r--r--users/flokli/ipu6-softisp/libcamera/0005-libcamera-shared_mem_object-reorganize-the-code-and-.patch403
-rw-r--r--users/flokli/ipu6-softisp/libcamera/0006-libcamera-software_isp-Add-SwStatsCpu-class.patch523
-rw-r--r--users/flokli/ipu6-softisp/libcamera/0007-libcamera-software_isp-Add-Debayer-base-class.patch255
-rw-r--r--users/flokli/ipu6-softisp/libcamera/0008-libcamera-software_isp-Add-DebayerCpu-class.patch825
-rw-r--r--users/flokli/ipu6-softisp/libcamera/0009-libcamera-ipa-add-Soft-IPA.patch506
-rw-r--r--users/flokli/ipu6-softisp/libcamera/0010-libcamera-introduce-SoftwareIsp.patch507
-rw-r--r--users/flokli/ipu6-softisp/libcamera/0011-libcamera-pipeline-simple-rename-converterBuffers_-a.patch240
-rw-r--r--users/flokli/ipu6-softisp/libcamera/0012-libcamera-pipeline-simple-enable-use-of-Soft-ISP-and.patch302
-rw-r--r--users/flokli/ipu6-softisp/libcamera/0013-libcamera-swstats_cpu-Add-support-for-8-10-and-12-bp.patch203
-rw-r--r--users/flokli/ipu6-softisp/libcamera/0014-libcamera-debayer_cpu-Add-support-for-8-10-and-12-bp.patch234
-rw-r--r--users/flokli/ipu6-softisp/libcamera/0015-libcamera-debayer_cpu-Add-BGR888-output-support.patch127
-rw-r--r--users/flokli/ipu6-softisp/libcamera/0016-libcamera-Add-support-for-IGIG_GBGR_IGIG_GRGB-bayer-.patch237
-rw-r--r--users/flokli/ipu6-softisp/libcamera/0017-libcamera-Add-Software-ISP-benchmarking-documentatio.patch132
-rw-r--r--users/flokli/ipu6-softisp/libcamera/0018-libcamera-software_isp-Apply-black-level-compensatio.patch396
-rw-r--r--users/flokli/ipu6-softisp/libcamera/0019-libcamera-Soft-IPA-use-CameraSensorHelper-for-analog.patch239
-rw-r--r--users/flokli/ipu6-softisp/libcamera/0020-ov01a1s-HACK.patch95
-rw-r--r--users/flokli/ipu6-softisp/libcamera/0021-libcamera-debayer_cpu-Make-the-minimum-size-1280x720.patch42
-rw-r--r--users/flokli/keyboards/dilemma/default.nix47
-rw-r--r--users/flokli/keyboards/dilemma/enable-taps.patch24
-rw-r--r--users/flokli/keyboards/dilemma/keymap.c220
-rw-r--r--users/flokli/keyboards/dilemma/rules.mk2
-rw-r--r--users/flokli/keyboards/k6_pro/default.nix41
-rw-r--r--users/flokli/keyboards/k6_pro/keymap.c76
-rw-r--r--users/flokli/keyboards/k6_pro/rules.mk2
-rw-r--r--users/flokli/nixos/.envrc1
-rw-r--r--users/flokli/nixos/.skip-subtree0
-rw-r--r--users/flokli/nixos/archeology-ec2/OWNERS1
-rw-r--r--users/flokli/nixos/archeology-ec2/configuration.nix35
-rw-r--r--users/flokli/nixos/archeology-ec2/hardware-configuration.nix36
-rw-r--r--users/flokli/nixos/archeology-ec2/parse-bucket-logs-continuously.py62
-rw-r--r--users/flokli/nixos/default.nix32
-rw-r--r--users/flokli/nixos/profiles/archeology.nix37
-rw-r--r--users/flokli/presentations/2023-09-09-nixcon-tvix/.gitignore1
-rw-r--r--users/flokli/presentations/2023-09-09-nixcon-tvix/architecture.dot5
-rw-r--r--users/flokli/presentations/2023-09-09-nixcon-tvix/cppnix-example-lexer.cpp13
-rw-r--r--users/flokli/presentations/2023-09-09-nixcon-tvix/crate-deps.dot19
-rw-r--r--users/flokli/presentations/2023-09-09-nixcon-tvix/default.nix37
-rw-r--r--users/flokli/presentations/2023-09-09-nixcon-tvix/presentation.md294
-rw-r--r--users/flokli/presentations/2023-09-09-nixcon-tvix/tvix-store-graph.svg17
-rw-r--r--users/flokli/presentations/2023-09-09-nixcon-tvix/tvixbolt.webmbin0 -> 983213 bytes
-rw-r--r--users/flokli/presentations/2023-09-13-asg-tvix-store/default.nix32
-rw-r--r--users/flokli/presentations/2023-09-13-asg-tvix-store/presentation.md138
-rw-r--r--users/flokli/presentations/2023-09-13-asg-tvix-store/tvix-store-graph-blob-directory.svg17
60 files changed, 25493 insertions, 3 deletions
diff --git a/users/flokli/OWNERS b/users/flokli/OWNERS
index 63e0fbda3c..a70f5eefff 100644
--- a/users/flokli/OWNERS
+++ b/users/flokli/OWNERS
@@ -1,3 +1,3 @@
-inherited: false
-owners:
-  - flokli
+set noparent
+
+flokli
diff --git a/users/flokli/archeology/OWNERS b/users/flokli/archeology/OWNERS
new file mode 100644
index 0000000000..b9bc074a80
--- /dev/null
+++ b/users/flokli/archeology/OWNERS
@@ -0,0 +1 @@
+edef
diff --git a/users/flokli/archeology/README.md b/users/flokli/archeology/README.md
new file mode 100644
index 0000000000..e4cd9b84b0
--- /dev/null
+++ b/users/flokli/archeology/README.md
@@ -0,0 +1,5 @@
+# archeology
+
+This directory contains various scripts and helpers used for nix-archeology tasks.
+
+It's used from some of the archeology instances, as well as standalone.
diff --git a/users/flokli/archeology/default.nix b/users/flokli/archeology/default.nix
new file mode 100644
index 0000000000..690944403b
--- /dev/null
+++ b/users/flokli/archeology/default.nix
@@ -0,0 +1,51 @@
+{ depot, pkgs, ... }:
+
+let
+  clickhouseConfigAWS = builtins.toFile "clickhouse-local.xml" ''
+    <clickhouse>
+        <s3>
+          <use_environment_credentials>true</use_environment_credentials>
+        </s3>
+    </clickhouse>
+  '';
+  # clickhouse has a very odd AWS config concept.
+  # Configure it to be a bit more sane.
+  clickhouseLocalFixedAWS = pkgs.runCommand "clickhouse-local-fixed"
+    {
+      nativeBuildInputs = [ pkgs.makeWrapper ];
+    } ''
+    mkdir -p $out/bin
+    makeWrapper ${pkgs.clickhouse}/bin/clickhouse-local $out/bin/clickhouse-local \
+      --append-flags "-C ${clickhouseConfigAWS}"
+  '';
+in
+
+depot.nix.readTree.drvTargets {
+  inherit clickhouseLocalFixedAWS;
+  parse-bucket-logs = pkgs.runCommand "archeology-parse-bucket-logs"
+    {
+      nativeBuildInputs = [ pkgs.makeWrapper ];
+    } ''
+    mkdir -p $out/bin
+    makeWrapper ${(pkgs.writers.writeRust "parse-bucket-logs-unwrapped" {} ./parse_bucket_logs.rs)} $out/bin/archeology-parse-bucket-logs \
+      --prefix PATH : ${pkgs.lib.makeBinPath [ clickhouseLocalFixedAWS ]}
+  '';
+
+  shell = pkgs.mkShell {
+    name = "archeology-shell";
+    packages = with pkgs; [ awscli2 clickhouseLocalFixedAWS rust-analyzer rustc rustfmt ];
+
+    AWS_PROFILE = "sso";
+    AWS_CONFIG_FILE = pkgs.writeText "aws-config" ''
+      [sso-session nixos]
+      sso_region = eu-north-1
+      sso_start_url = https://nixos.awsapps.com/start
+      sso_registration_scopes = sso:account:access
+
+      [profile "sso"]
+      sso_session = nixos
+      sso_account_id = 080433136561
+      sso_role_name = archeologist
+    '';
+  };
+}
diff --git a/users/flokli/archeology/parse_bucket_logs.rs b/users/flokli/archeology/parse_bucket_logs.rs
new file mode 100644
index 0000000000..3ab2e133b3
--- /dev/null
+++ b/users/flokli/archeology/parse_bucket_logs.rs
@@ -0,0 +1,42 @@
+use std::env;
+use std::process::Command;
+use std::process::ExitCode;
+
+fn main() -> ExitCode {
+    let args: Vec<String> = env::args().collect();
+    if args.len() != 3 {
+        eprintln!("needs two args, input s3 url (glob) and output pq file");
+        return ExitCode::FAILURE;
+    }
+
+    let input_files = &args[1];
+    let output_file = &args[2];
+
+    let mut cmd = Command::new("clickhouse-local");
+    cmd.arg("--progress")
+        .arg("-q")
+        .arg(format!(r#"SELECT
+        key,
+        toInt64(nullif(http_status, '-')) AS http_status,
+        toInt64(nullif(object_size_str, '-')) AS object_size,
+        toInt64(nullif(bytes_sent_str, '-')) AS bytes_sent,
+        nullif(user_agent, '-') AS user_agent,
+        operation,
+        nullif(requester, '-') AS requester,
+        parseDateTime(timestamp_str, '%d/%b/%Y:%k:%i:%s %z') AS timestamp
+    FROM s3(
+        '{}',
+        'Regexp',
+        'owner String , bucket String, timestamp_str String, remote_ip String, requester LowCardinality(String), request_id String, operation LowCardinality(String), key String, request_uri String, http_status String, error_code String, bytes_sent_str String, object_size_str String, total_time String, turn_around_time String, referer String, user_agent String, version_id String, host_id String, signature_version String, cipher_suite String, authentication_type String, host_header String, tls_version String, access_point_arn String, acl_required String'
+    )
+    ORDER BY timestamp ASC
+    SETTINGS
+        format_regexp_skip_unmatched = 1,
+        format_regexp = '(\\S+) (\\S+) \\[(.*)\\] (\\S+) (\\S+) (\\S+) (\\S+) (\\S+) ((?:\\S+ \\S+ \\S+)|\\S+) (\\S+) (\\S+) (\\S+) (\\S+) (\\S+) (\\S+) (\\S+) ("\\S+") (\\S+) (\\S+) (\\S+) (\\S+) (\\S+) (\\S+) (\\S+) (\\S+) (\\S+).*',
+        output_format_parquet_compression_method = 'zstd'
+    INTO OUTFILE '{}' FORMAT Parquet"#, input_files, output_file));
+
+    cmd.status().expect("clickhouse-local failed");
+
+    ExitCode::SUCCESS
+}
diff --git a/users/flokli/archivist/OWNERS b/users/flokli/archivist/OWNERS
new file mode 100644
index 0000000000..b9bc074a80
--- /dev/null
+++ b/users/flokli/archivist/OWNERS
@@ -0,0 +1 @@
+edef
diff --git a/users/flokli/archivist/default.nix b/users/flokli/archivist/default.nix
new file mode 100644
index 0000000000..ef49c46db2
--- /dev/null
+++ b/users/flokli/archivist/default.nix
@@ -0,0 +1,28 @@
+{ depot
+, pkgs
+, ...
+}:
+depot.nix.readTree.drvTargets {
+  shell = pkgs.mkShell {
+    name = "archivist-shell";
+    packages = with pkgs; [ awscli2 ];
+
+    AWS_PROFILE = "archivist";
+    AWS_CONFIG_FILE = pkgs.writeText "aws-config" ''
+      [sso-session nixos]
+      sso_region = eu-north-1
+      sso_start_url = https://nixos.awsapps.com/start
+      sso_registration_scopes = sso:account:access
+
+      [profile "archivist"]
+      sso_session = nixos
+      sso_account_id = 286553126452
+      sso_role_name = AWSAdministratorAccess
+
+      [profile "archeologist"]
+      sso_session = nixos
+      sso_account_id = 080433136561
+      sso_role_name = archeologist
+    '';
+  };
+}
diff --git a/users/flokli/ipu6-softisp/README.md b/users/flokli/ipu6-softisp/README.md
new file mode 100644
index 0000000000..2ab727ace4
--- /dev/null
+++ b/users/flokli/ipu6-softisp/README.md
@@ -0,0 +1,29 @@
+# ipu6-softisp
+
+This code adds support for the ipu6 webcams via libcamera, based on the work in
+https://copr.fedorainfracloud.org/coprs/jwrdegoede/ipu6-softisp/.
+
+It's supposed to be included in your NixOS configuration imports, and will:
+
+ - Add some patches to your kernel, which should apply on 6.8.x
+ - Add the `ipu6-camera-bins` firmware (still needed)
+ - Enable some kernel config options
+ - Add an udev rule so libcamera can do DMABUF things
+ - Override `services.pipewire.package` and
+   `services.pipewire.wireplumber.package` to use a pipewire built with a libcamera
+   with support for this webcam.
+
+Please make sure you don't have any of the `hardware.ipu6` options still
+enabled, as they use the closed-source userspace stack and will conflict.
+
+Also make sure to track nixos-unstable for this. This code will get periodically
+updated to be compatible with nixos-unstable!
+
+The testing instructions from
+https://copr.fedorainfracloud.org/coprs/jwrdegoede/ipu6-softisp/ still apply.
+
+`qcam` can be found in `libcamera-qcam` (pending on
+https://github.com/NixOS/nixpkgs/pull/284964 to trickle into master).
+
+Thanks to Hans de Goede for helping me bringing this up, as well as to
+puckipedia for sorting out some pipewire-related confusion.
diff --git a/users/flokli/ipu6-softisp/config.nix b/users/flokli/ipu6-softisp/config.nix
new file mode 100644
index 0000000000..95cb3e5c25
--- /dev/null
+++ b/users/flokli/ipu6-softisp/config.nix
@@ -0,0 +1,74 @@
+{ pkgs, lib, ... }:
+
+let
+  libcamera = pkgs.libcamera.overrideAttrs (old: {
+    mesonFlags = old.mesonFlags or [ ] ++ [
+      "-Dpipelines=simple,ipu3,uvcvideo"
+      "-Dipas=simple,ipu3"
+    ];
+
+    # 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.
+    # It was later refreshed with https://patchwork.libcamera.org/cover/19663/
+    patches = old.patches or [ ] ++ [
+      ./libcamera/0001-libcamera-pipeline-simple-fix-size-adjustment-in-val.patch
+      ./libcamera/0002-libcamera-internal-Move-dma_heaps.-h-cpp-to-common-d.patch
+      ./libcamera/0003-libcamera-dma_heaps-extend-DmaHeap-class-to-support-.patch
+      ./libcamera/0004-libcamera-internal-Move-SharedMemObject-class-to-a-c.patch
+      ./libcamera/0005-libcamera-shared_mem_object-reorganize-the-code-and-.patch
+      ./libcamera/0006-libcamera-software_isp-Add-SwStatsCpu-class.patch
+      ./libcamera/0007-libcamera-software_isp-Add-Debayer-base-class.patch
+      ./libcamera/0008-libcamera-software_isp-Add-DebayerCpu-class.patch
+      ./libcamera/0009-libcamera-ipa-add-Soft-IPA.patch
+      ./libcamera/0010-libcamera-introduce-SoftwareIsp.patch
+      ./libcamera/0011-libcamera-pipeline-simple-rename-converterBuffers_-a.patch
+      ./libcamera/0012-libcamera-pipeline-simple-enable-use-of-Soft-ISP-and.patch
+      ./libcamera/0013-libcamera-swstats_cpu-Add-support-for-8-10-and-12-bp.patch
+      ./libcamera/0014-libcamera-debayer_cpu-Add-support-for-8-10-and-12-bp.patch
+      ./libcamera/0015-libcamera-debayer_cpu-Add-BGR888-output-support.patch
+      ./libcamera/0016-libcamera-Add-support-for-IGIG_GBGR_IGIG_GRGB-bayer-.patch
+      ./libcamera/0017-libcamera-Add-Software-ISP-benchmarking-documentatio.patch
+      ./libcamera/0018-libcamera-software_isp-Apply-black-level-compensatio.patch
+      ./libcamera/0019-libcamera-Soft-IPA-use-CameraSensorHelper-for-analog.patch
+      ./libcamera/0020-ov01a1s-HACK.patch
+      ./libcamera/0021-libcamera-debayer_cpu-Make-the-minimum-size-1280x720.patch
+    ];
+  });
+
+  # use patched libcamera
+  pipewire' = (pkgs.pipewire.override {
+    inherit libcamera;
+  });
+
+  wireplumber' = (pkgs.wireplumber.override {
+    pipewire = pipewire';
+  });
+in
+{
+  boot.kernelPatches = [{
+    name = "linux-kernel-test.patch";
+    patch = ./kernel/softisp.patch;
+    extraStructuredConfig = {
+      # needed for /dev/dma_heap
+      DMABUF_HEAPS_CMA = lib.kernel.yes;
+      DMABUF_HEAPS_SYSTEM = lib.kernel.yes;
+      DMABUF_HEAPS = lib.kernel.yes;
+    };
+  }];
+
+
+  services.udev.extraRules = ''
+    KERNEL=="system", SUBSYSTEM=="dma_heap", TAG+="uaccess"
+  '';
+
+  # provide qcam in $PATH.
+  environment.systemPackages = [
+    (libcamera.override {
+      withQcam = true;
+    })
+  ];
+
+  services.pipewire.package = pipewire';
+  services.pipewire.wireplumber.package = wireplumber';
+}
diff --git a/users/flokli/ipu6-softisp/default.nix b/users/flokli/ipu6-softisp/default.nix
new file mode 100644
index 0000000000..1f603dbb42
--- /dev/null
+++ b/users/flokli/ipu6-softisp/default.nix
@@ -0,0 +1,57 @@
+# This file ensures the fixes from ./config.nix build with the version of
+# nixpkgs from depot.
+# If you're an outside user of this, import config.nix as a NixOS module (and
+# check the README.md file).
+
+{ depot
+, pkgs
+, ...
+}:
+
+let
+  systemFor = sys: (depot.ops.nixos.nixosFor sys).system;
+in
+depot.nix.readTree.drvTargets rec {
+  testSystem = systemFor ({ modulesPath, pkgs, ... }: {
+    imports = [
+      # Import the module, this is something a user would do in their config.
+      ./config.nix
+    ];
+
+    # Make sure we use the linuxPackages_latest.
+    boot.kernelPackages = pkgs.linuxPackages_latest;
+
+    # Enable firmware.
+    hardware.enableAllFirmware = true;
+
+    # Set some options necessary to evaluate.
+    boot.loader.systemd-boot.enable = true;
+    fileSystems."/" = {
+      device = "/dev/disk/by-partlabel/root";
+      fsType = "xfs";
+    };
+
+    # Enable pipewire and wireplumber.
+    services.pipewire = {
+      enable = true;
+      wireplumber.enable = true;
+    };
+
+    # Shut off the warning.
+    system.stateVersion = "24.05";
+  });
+
+  # Make sure the firmware requested by the driver is present in our firmware.
+  # We do have a .xz suffix here, but that's fine, since request_firmware does
+  # check ${name}.xz too in case CONFIG_FW_LOADER_COMPRESS is set.
+  # The path needs to be kept in sync with the ones used in the kernel patch.
+  checkFirmware = pkgs.runCommand "check-firmware" { } ''
+    stat ${testSystem}/firmware/intel/ipu/ipu6se_fw.bin.xz
+    stat ${testSystem}/firmware/intel/ipu/ipu6ep_fw.bin.xz
+    stat ${testSystem}/firmware/intel/ipu/ipu6_fw.bin.xz
+    stat ${testSystem}/firmware/intel/ipu/ipu6epmtl_fw.bin.xz
+
+    # all good, succeed build
+    touch $out
+  '';
+}
diff --git a/users/flokli/ipu6-softisp/kernel/.skip-tree b/users/flokli/ipu6-softisp/kernel/.skip-tree
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/users/flokli/ipu6-softisp/kernel/.skip-tree
diff --git a/users/flokli/ipu6-softisp/kernel/softisp.patch b/users/flokli/ipu6-softisp/kernel/softisp.patch
new file mode 100644
index 0000000000..8731ed914c
--- /dev/null
+++ b/users/flokli/ipu6-softisp/kernel/softisp.patch
@@ -0,0 +1,18077 @@
+From 037f05a9772f3243907bb826e913971ee20e9487 Mon Sep 17 00:00:00 2001
+From: Sakari Ailus <sakari.ailus@linux.intel.com>
+Date: Tue, 8 Aug 2023 10:55:31 +0300
+Subject: [PATCH 01/33] media: mc: Add INTERNAL pad flag
+
+Internal source pads will be used as routing endpoints in V4L2
+[GS]_ROUTING IOCTLs, to indicate that the stream begins in the entity.
+
+Also prevent creating links to pads that have been flagged as internal.
+
+Signed-off-by: Sakari Ailus <sakari.ailus@linux.intel.com>
+---
+ Documentation/userspace-api/media/glossary.rst             | 6 ++++++
+ Documentation/userspace-api/media/mediactl/media-types.rst | 6 ++++++
+ drivers/media/mc/mc-entity.c                               | 6 +++++-
+ include/uapi/linux/media.h                                 | 1 +
+ 4 files changed, 18 insertions(+), 1 deletion(-)
+
+diff --git a/Documentation/userspace-api/media/glossary.rst b/Documentation/userspace-api/media/glossary.rst
+index 96a360edbf3b..f7b99a4527c7 100644
+--- a/Documentation/userspace-api/media/glossary.rst
++++ b/Documentation/userspace-api/media/glossary.rst
+@@ -173,6 +173,12 @@ Glossary
+ 	An integrated circuit that integrates all components of a computer
+ 	or other electronic systems.
+ 
++_media-glossary-stream:
++    Stream
++	A distinct flow of data (image data or metadata) over a media pipeline
++	from source to sink. A source may be e.g. an image sensor and a sink
++	e.g. a memory buffer.
++
+     V4L2 API
+ 	**V4L2 userspace API**
+ 
+diff --git a/Documentation/userspace-api/media/mediactl/media-types.rst b/Documentation/userspace-api/media/mediactl/media-types.rst
+index 0ffeece1e0c8..28941da27790 100644
+--- a/Documentation/userspace-api/media/mediactl/media-types.rst
++++ b/Documentation/userspace-api/media/mediactl/media-types.rst
+@@ -361,6 +361,7 @@ Types and flags used to represent the media graph elements
+ .. _MEDIA-PAD-FL-SINK:
+ .. _MEDIA-PAD-FL-SOURCE:
+ .. _MEDIA-PAD-FL-MUST-CONNECT:
++.. _MEDIA-PAD-FL-INTERNAL:
+ 
+ .. flat-table:: Media pad flags
+     :header-rows:  0
+@@ -382,6 +383,11 @@ Types and flags used to represent the media graph elements
+ 	  when this flag isn't set; the absence of the flag doesn't imply
+ 	  there is none.
+ 
++    *  -  ``MEDIA_PAD_FL_INTERNAL``
++       -  The internal flag indicates an internal pad that has no external
++	  connections. Such a pad shall not be connected with a link. The
++	  internal flag indicates that the :ref:``stream
++	  <media-glossary-stream>`` either starts or ends in the entity.
+ 
+ One and only one of ``MEDIA_PAD_FL_SINK`` and ``MEDIA_PAD_FL_SOURCE``
+ must be set for every pad.
+diff --git a/drivers/media/mc/mc-entity.c b/drivers/media/mc/mc-entity.c
+index 543a392f8635..1fc80fd3e5e3 100644
+--- a/drivers/media/mc/mc-entity.c
++++ b/drivers/media/mc/mc-entity.c
+@@ -1075,7 +1075,8 @@ int media_get_pad_index(struct media_entity *entity, u32 pad_type,
+ 
+ 	for (i = 0; i < entity->num_pads; i++) {
+ 		if ((entity->pads[i].flags &
+-		     (MEDIA_PAD_FL_SINK | MEDIA_PAD_FL_SOURCE)) != pad_type)
++		     (MEDIA_PAD_FL_SINK | MEDIA_PAD_FL_SOURCE |
++		      MEDIA_PAD_FL_INTERNAL)) != pad_type)
+ 			continue;
+ 
+ 		if (entity->pads[i].sig_type == sig_type)
+@@ -1098,6 +1099,9 @@ media_create_pad_link(struct media_entity *source, u16 source_pad,
+ 		return -EINVAL;
+ 	if (WARN_ON(!(source->pads[source_pad].flags & MEDIA_PAD_FL_SOURCE)))
+ 		return -EINVAL;
++	if (WARN_ON(source->pads[source_pad].flags & MEDIA_PAD_FL_SOURCE &&
++		    source->pads[source_pad].flags & MEDIA_PAD_FL_INTERNAL))
++		return -EINVAL;
+ 	if (WARN_ON(!(sink->pads[sink_pad].flags & MEDIA_PAD_FL_SINK)))
+ 		return -EINVAL;
+ 
+diff --git a/include/uapi/linux/media.h b/include/uapi/linux/media.h
+index 1c80b1d6bbaf..80cfd12a43fc 100644
+--- a/include/uapi/linux/media.h
++++ b/include/uapi/linux/media.h
+@@ -208,6 +208,7 @@ struct media_entity_desc {
+ #define MEDIA_PAD_FL_SINK			(1U << 0)
+ #define MEDIA_PAD_FL_SOURCE			(1U << 1)
+ #define MEDIA_PAD_FL_MUST_CONNECT		(1U << 2)
++#define MEDIA_PAD_FL_INTERNAL			(1U << 3)
+ 
+ struct media_pad_desc {
+ 	__u32 entity;		/* entity ID */
+-- 
+2.43.2
+
+
+From 5f0cdae874f1c0237936c2c12a9fc019b93de4c9 Mon Sep 17 00:00:00 2001
+From: Sakari Ailus <sakari.ailus@linux.intel.com>
+Date: Tue, 8 Aug 2023 10:55:32 +0300
+Subject: [PATCH 02/33] media: uapi: Add generic serial metadata mbus formats
+
+Add generic serial metadata mbus formats. These formats describe data
+width and packing but not the content itself. The reason for specifying
+such formats is that the formats as such are fairly device specific but
+they are still handled by CSI-2 receiver drivers that should not be aware
+of device specific formats. What makes generic metadata formats possible
+is that these formats are parsed by software only, after capturing the
+data to system memory.
+
+Signed-off-by: Sakari Ailus <sakari.ailus@linux.intel.com>
+---
+ .../media/v4l/subdev-formats.rst              | 257 ++++++++++++++++++
+ include/uapi/linux/media-bus-format.h         |   9 +
+ 2 files changed, 266 insertions(+)
+
+diff --git a/Documentation/userspace-api/media/v4l/subdev-formats.rst b/Documentation/userspace-api/media/v4l/subdev-formats.rst
+index eb3cd20b0cf2..7d107873cddd 100644
+--- a/Documentation/userspace-api/media/v4l/subdev-formats.rst
++++ b/Documentation/userspace-api/media/v4l/subdev-formats.rst
+@@ -8306,3 +8306,260 @@ The following table lists the existing metadata formats.
+ 	both sides of the link and the bus format is a fixed
+ 	metadata format that is not configurable from userspace.
+ 	Width and height will be set to 0 for this format.
++
++Generic Serial Metadata Formats
++^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++
++Generic serial metadata formats are used on serial busses where the actual data
++content is more or less device specific but the data is transmitted and received
++by multiple devices that do not process the data in any way, simply writing
++it to system memory for processing in software at the end of the pipeline.
++
++The more specific variant describing the actual data is used on the internal
++source pad of the originating sub-device.
++
++"b" in an array cell signifies a byte of data, followed by the number of byte
++and finally the bit number in subscript. "p" indicates a padding bit.
++
++.. _media-bus-format-generic-meta:
++
++.. cssclass: longtable
++
++.. flat-table:: Generic Serial Metadata Formats
++    :header-rows:  2
++    :stub-columns: 0
++
++    * - Identifier
++      - Code
++      -
++      - :cspan:`23` Data organization
++    * -
++      -
++      - Bit
++      - 23
++      - 22
++      - 21
++      - 20
++      - 19
++      - 18
++      - 17
++      - 16
++      - 15
++      - 14
++      - 13
++      - 12
++      - 11
++      - 10
++      - 9
++      - 8
++      - 7
++      - 6
++      - 5
++      - 4
++      - 3
++      - 2
++      - 1
++      - 0
++    * .. _MEDIA-BUS-FMT-META-8:
++
++      - MEDIA_BUS_FMT_META_8
++      - 0x8001
++      -
++      -
++      -
++      -
++      -
++      -
++      -
++      -
++      -
++      -
++      -
++      -
++      -
++      -
++      -
++      -
++      -
++      - b0\ :sub:`7`
++      - b0\ :sub:`6`
++      - b0\ :sub:`5`
++      - b0\ :sub:`4`
++      - b0\ :sub:`3`
++      - b0\ :sub:`2`
++      - b0\ :sub:`1`
++      - b0\ :sub:`0`
++    * .. _MEDIA-BUS-FMT-META-10:
++
++      - MEDIA_BUS_FMT_META_10
++      - 0x8002
++      -
++      -
++      -
++      -
++      -
++      -
++      -
++      -
++      -
++      -
++      -
++      -
++      -
++      -
++      -
++      - b0\ :sub:`7`
++      - b0\ :sub:`6`
++      - b0\ :sub:`5`
++      - b0\ :sub:`4`
++      - b0\ :sub:`3`
++      - b0\ :sub:`2`
++      - b0\ :sub:`1`
++      - b0\ :sub:`0`
++      - p
++      - p
++    * .. _MEDIA-BUS-FMT-META-12:
++
++      - MEDIA_BUS_FMT_META_12
++      - 0x8003
++      -
++      -
++      -
++      -
++      -
++      -
++      -
++      -
++      -
++      -
++      -
++      -
++      -
++      - b0\ :sub:`7`
++      - b0\ :sub:`6`
++      - b0\ :sub:`5`
++      - b0\ :sub:`4`
++      - b0\ :sub:`3`
++      - b0\ :sub:`2`
++      - b0\ :sub:`1`
++      - b0\ :sub:`0`
++      - p
++      - p
++      - p
++      - p
++    * .. _MEDIA-BUS-FMT-META-14:
++
++      - MEDIA_BUS_FMT_META_14
++      - 0x8004
++      -
++      -
++      -
++      -
++      -
++      -
++      -
++      -
++      -
++      -
++      -
++      - b0\ :sub:`7`
++      - b0\ :sub:`6`
++      - b0\ :sub:`5`
++      - b0\ :sub:`4`
++      - b0\ :sub:`3`
++      - b0\ :sub:`2`
++      - b0\ :sub:`1`
++      - b0\ :sub:`0`
++      - p
++      - p
++      - p
++      - p
++      - p
++      - p
++    * .. _MEDIA-BUS-FMT-META-16:
++
++      - MEDIA_BUS_FMT_META_16
++      - 0x8005
++      -
++      -
++      -
++      -
++      -
++      -
++      -
++      -
++      -
++      - b0\ :sub:`7`
++      - b0\ :sub:`6`
++      - b0\ :sub:`5`
++      - b0\ :sub:`4`
++      - b0\ :sub:`3`
++      - b0\ :sub:`2`
++      - b0\ :sub:`1`
++      - b0\ :sub:`0`
++      - p
++      - p
++      - p
++      - p
++      - p
++      - p
++      - p
++      - p
++    * .. _MEDIA-BUS-FMT-META-20:
++
++      - MEDIA_BUS_FMT_META_20
++      - 0x8007
++      -
++      -
++      -
++      -
++      -
++      - b0\ :sub:`7`
++      - b0\ :sub:`6`
++      - b0\ :sub:`5`
++      - b0\ :sub:`4`
++      - b0\ :sub:`3`
++      - b0\ :sub:`2`
++      - b0\ :sub:`1`
++      - b0\ :sub:`0`
++      - p
++      - p
++      - p
++      - p
++      - p
++      - p
++      - p
++      - p
++      - p
++      - p
++      - p
++      - p
++    * .. _MEDIA-BUS-FMT-META-24:
++
++      - MEDIA_BUS_FMT_META_24
++      - 0x8009
++      -
++      - b0\ :sub:`7`
++      - b0\ :sub:`6`
++      - b0\ :sub:`5`
++      - b0\ :sub:`4`
++      - b0\ :sub:`3`
++      - b0\ :sub:`2`
++      - b0\ :sub:`1`
++      - b0\ :sub:`0`
++      - p
++      - p
++      - p
++      - p
++      - p
++      - p
++      - p
++      - p
++      - p
++      - p
++      - p
++      - p
++      - p
++      - p
++      - p
++      - p
+diff --git a/include/uapi/linux/media-bus-format.h b/include/uapi/linux/media-bus-format.h
+index f05f747e444d..d4c1d991014b 100644
+--- a/include/uapi/linux/media-bus-format.h
++++ b/include/uapi/linux/media-bus-format.h
+@@ -174,4 +174,13 @@
+  */
+ #define MEDIA_BUS_FMT_METADATA_FIXED		0x7001
+ 
++/* Generic line based metadata formats for serial buses. Next is 0x8008. */
++#define MEDIA_BUS_FMT_META_8			0x8001
++#define MEDIA_BUS_FMT_META_10			0x8002
++#define MEDIA_BUS_FMT_META_12			0x8003
++#define MEDIA_BUS_FMT_META_14			0x8004
++#define MEDIA_BUS_FMT_META_16			0x8005
++#define MEDIA_BUS_FMT_META_20			0x8006
++#define MEDIA_BUS_FMT_META_24			0x8007
++
+ #endif /* __LINUX_MEDIA_BUS_FORMAT_H */
+-- 
+2.43.2
+
+
+From 8af4eeaee34159605ec86b57fa638a82fd968f31 Mon Sep 17 00:00:00 2001
+From: Sakari Ailus <sakari.ailus@linux.intel.com>
+Date: Tue, 8 Aug 2023 10:55:33 +0300
+Subject: [PATCH 03/33] media: uapi: Document which mbus format fields are
+ valid for metadata
+
+Now that metadata mbus formats have been added, it is necessary to define
+which fields in struct v4l2_mbus_format are applicable to them (not many).
+
+Signed-off-by: Sakari Ailus <sakari.ailus@linux.intel.com>
+---
+ include/uapi/linux/v4l2-mediabus.h | 18 ++++++++++++------
+ 1 file changed, 12 insertions(+), 6 deletions(-)
+
+diff --git a/include/uapi/linux/v4l2-mediabus.h b/include/uapi/linux/v4l2-mediabus.h
+index 6b07b73473b5..3cadb3b58b85 100644
+--- a/include/uapi/linux/v4l2-mediabus.h
++++ b/include/uapi/linux/v4l2-mediabus.h
+@@ -19,12 +19,18 @@
+  * @width:	image width
+  * @height:	image height
+  * @code:	data format code (from enum v4l2_mbus_pixelcode)
+- * @field:	used interlacing type (from enum v4l2_field)
+- * @colorspace:	colorspace of the data (from enum v4l2_colorspace)
+- * @ycbcr_enc:	YCbCr encoding of the data (from enum v4l2_ycbcr_encoding)
+- * @hsv_enc:	HSV encoding of the data (from enum v4l2_hsv_encoding)
+- * @quantization: quantization of the data (from enum v4l2_quantization)
+- * @xfer_func:  transfer function of the data (from enum v4l2_xfer_func)
++ * @field:	used interlacing type (from enum v4l2_field), not applicable
++ *		to metadata mbus codes
++ * @colorspace:	colorspace of the data (from enum v4l2_colorspace), zero on
++ *		metadata mbus codes
++ * @ycbcr_enc:	YCbCr encoding of the data (from enum v4l2_ycbcr_encoding), zero
++ *		on metadata mbus codes
++ * @hsv_enc:	HSV encoding of the data (from enum v4l2_hsv_encoding), zero on
++ *		metadata mbus codes
++ * @quantization: quantization of the data (from enum v4l2_quantization), zero
++ *		on metadata mbus codes
++ * @xfer_func:  transfer function of the data (from enum v4l2_xfer_func), zero
++ *		on metadata mbus codes
+  * @flags:	flags (V4L2_MBUS_FRAMEFMT_*)
+  * @reserved:  reserved bytes that can be later used
+  */
+-- 
+2.43.2
+
+
+From ed5884d40def9adfa77841427e52733746158a77 Mon Sep 17 00:00:00 2001
+From: Sakari Ailus <sakari.ailus@linux.intel.com>
+Date: Tue, 8 Aug 2023 10:55:34 +0300
+Subject: [PATCH 04/33] media: uapi: Add a macro to tell whether an mbus code
+ is metadata
+
+Add a macro to tell whether a given mbus code is metadata.
+
+Signed-off-by: Sakari Ailus <sakari.ailus@linux.intel.com>
+---
+ include/uapi/linux/media-bus-format.h | 3 +++
+ 1 file changed, 3 insertions(+)
+
+diff --git a/include/uapi/linux/media-bus-format.h b/include/uapi/linux/media-bus-format.h
+index d4c1d991014b..6fcd7d276bc6 100644
+--- a/include/uapi/linux/media-bus-format.h
++++ b/include/uapi/linux/media-bus-format.h
+@@ -183,4 +183,7 @@
+ #define MEDIA_BUS_FMT_META_20			0x8006
+ #define MEDIA_BUS_FMT_META_24			0x8007
+ 
++#define MEDIA_BUS_FMT_IS_META(code)		\
++	((code) & 0xf000 == 0x7000 || (code) & 0xf000 == 0x8000)
++
+ #endif /* __LINUX_MEDIA_BUS_FORMAT_H */
+-- 
+2.43.2
+
+
+From c0e682a815c5baf012c8963968385c197e7e0943 Mon Sep 17 00:00:00 2001
+From: Sakari Ailus <sakari.ailus@linux.intel.com>
+Date: Tue, 8 Aug 2023 10:55:35 +0300
+Subject: [PATCH 05/33] media: uapi: Add generic 8-bit metadata format
+ definitions
+
+Generic 8-bit metadata formats define the in-memory data layout but not
+the format of the data itself. The reasoning for having such formats is to
+allow CSI-2 receiver drivers to receive and DMA drivers to write the data
+to memory without knowing a large number of device specific formats.
+
+These formats may be used only in conjunction of a Media controller
+pipeline where the internal pad of the source sub-device defines the
+specific format of the data (using an mbus code).
+
+Signed-off-by: Sakari Ailus <sakari.ailus@linux.intel.com>
+---
+ .../userspace-api/media/v4l/meta-formats.rst  |   1 +
+ .../media/v4l/metafmt-generic.rst             | 331 ++++++++++++++++++
+ drivers/media/v4l2-core/v4l2-ioctl.c          |   8 +
+ include/uapi/linux/videodev2.h                |   9 +
+ 4 files changed, 349 insertions(+)
+ create mode 100644 Documentation/userspace-api/media/v4l/metafmt-generic.rst
+
+diff --git a/Documentation/userspace-api/media/v4l/meta-formats.rst b/Documentation/userspace-api/media/v4l/meta-formats.rst
+index 0bb61fc5bc00..919f595576b9 100644
+--- a/Documentation/userspace-api/media/v4l/meta-formats.rst
++++ b/Documentation/userspace-api/media/v4l/meta-formats.rst
+@@ -19,3 +19,4 @@ These formats are used for the :ref:`metadata` interface only.
+     metafmt-vsp1-hgo
+     metafmt-vsp1-hgt
+     metafmt-vivid
++    metafmt-generic
+diff --git a/Documentation/userspace-api/media/v4l/metafmt-generic.rst b/Documentation/userspace-api/media/v4l/metafmt-generic.rst
+new file mode 100644
+index 000000000000..a27bfc721edf
+--- /dev/null
++++ b/Documentation/userspace-api/media/v4l/metafmt-generic.rst
+@@ -0,0 +1,331 @@
++.. SPDX-License-Identifier: GPL-2.0 OR GFDL-1.1-no-invariants-or-later
++
++**************************************************************************************************************************************************************************************************************************************************************************************************************************
++V4L2_META_FMT_GENERIC_8 ('MET8'), V4L2_META_FMT_GENERIC_CSI2_10 ('MC1A'), V4L2_META_FMT_GENERIC_CSI2_12 ('MC1C'), V4L2_META_FMT_GENERIC_CSI2_14 ('MC1E'), V4L2_META_FMT_GENERIC_CSI2_16 ('MC1G'), V4L2_META_FMT_GENERIC_CSI2_20 ('MC1K'), V4L2_META_FMT_GENERIC_CSI2_24 ('MC1O'), V4L2_META_FMT_GENERIC_CSI2_2_24 ('MC2O')
++**************************************************************************************************************************************************************************************************************************************************************************************************************************
++
++
++Generic line-based metadata formats
++
++
++Description
++===========
++
++These generic line-based metadata formats define the memory layout of the data
++without defining the format or meaning of the metadata itself. These formats may
++only be used with a Media controller pipeline where the more specific format is
++defined in an :ref:`internal source pad <MEDIA-PAD-FL-INTERNAL>` of the source
++sub-device. See also :ref:`source routes <v4l2-subdev-source-routes>`.
++
++.. _v4l2-meta-fmt-generic-8:
++
++V4L2_META_FMT_GENERIC_8
++-----------------------
++
++The V4L2_META_FMT_GENERIC_8 format is a plain 8-bit metadata format.
++
++This format is also used on CSI-2 on both 8 bits per sample as well as on
++16 bits per sample when two bytes of metadata are packed into one sample.
++
++**Byte Order Of V4L2_META_FMT_GENERIC_8.**
++Each cell is one byte. "M" denotes a byte of metadata.
++
++.. tabularcolumns:: |p{2.4cm}|p{1.2cm}|p{1.2cm}|p{1.2cm}|p{1.2cm}|
++
++.. flat-table::
++    :header-rows:  0
++    :stub-columns: 0
++    :widths: 12 8 8 8 8
++
++    * - start + 0:
++      - M\ :sub:`00`
++      - M\ :sub:`10`
++      - M\ :sub:`20`
++      - M\ :sub:`30`
++    * - start + 4:
++      - M\ :sub:`01`
++      - M\ :sub:`11`
++      - M\ :sub:`21`
++      - M\ :sub:`31`
++
++.. _v4l2-meta-fmt-generic-csi2-10:
++
++V4L2_META_FMT_GENERIC_CSI2_10
++-----------------------------
++
++V4L2_META_FMT_GENERIC_CSI2_10 contains packed 8-bit generic metadata, 10 bits
++for each 8 bits of data. Every four bytes of metadata is followed by a single
++byte of padding. The way the data is stored follows the CSI-2 specification.
++
++This format is also used on CSI-2 on 20 bits per sample format that packs two
++bytes of metadata into one sample.
++
++This format is little endian.
++
++**Byte Order Of V4L2_META_FMT_GENERIC_CSI2_10.**
++Each cell is one byte. "M" denotes a byte of metadata and "p" a byte of padding.
++
++.. tabularcolumns:: |p{2.4cm}|p{1.2cm}|p{1.2cm}|p{1.2cm}|p{1.2cm}|p{.8cm}|
++
++.. flat-table::
++    :header-rows:  0
++    :stub-columns: 0
++    :widths: 12 8 8 8 8 8
++
++    * - start + 0:
++      - M\ :sub:`00`
++      - M\ :sub:`10`
++      - M\ :sub:`20`
++      - M\ :sub:`30`
++      - p
++    * - start + 5:
++      - M\ :sub:`01`
++      - M\ :sub:`11`
++      - M\ :sub:`21`
++      - M\ :sub:`31`
++      - p
++
++.. _v4l2-meta-fmt-generic-csi2-12:
++
++V4L2_META_FMT_GENERIC_CSI2_12
++-----------------------------
++
++V4L2_META_FMT_GENERIC_CSI2_12 contains packed 8-bit generic metadata, 12 bits
++for each 8 bits of data. Every four bytes of metadata is followed by two bytes
++of padding. The way the data is stored follows the CSI-2 specification.
++
++This format is little endian.
++
++**Byte Order Of V4L2_META_FMT_GENERIC_CSI2_12.**
++Each cell is one byte. "M" denotes a byte of metadata and "p" a byte of padding.
++
++.. tabularcolumns:: |p{2.4cm}|p{1.2cm}|p{1.2cm}|p{1.2cm}|p{1.2cm}|p{.8cm}|p{.8cm}|
++
++.. flat-table::
++    :header-rows:  0
++    :stub-columns: 0
++    :widths: 12 8 8 8 8 8 8
++
++    * - start + 0:
++      - M\ :sub:`00`
++      - M\ :sub:`10`
++      - M\ :sub:`20`
++      - M\ :sub:`30`
++      - p
++      - p
++    * - start + 6:
++      - M\ :sub:`01`
++      - M\ :sub:`11`
++      - M\ :sub:`21`
++      - M\ :sub:`31`
++      - p
++      - p
++
++.. _v4l2-meta-fmt-generic-csi2-14:
++
++V4L2_META_FMT_GENERIC_CSI2_14
++-----------------------------
++
++V4L2_META_FMT_GENERIC_CSI2_14 contains packed 8-bit generic metadata, 14 bits
++for each 8 bits of data. Every four bytes of metadata is followed by three
++bytes of padding. The way the data is stored follows the CSI-2 specification.
++
++This format is little endian.
++
++**Byte Order Of V4L2_META_FMT_GENERIC_CSI2_14.**
++Each cell is one byte. "M" denotes a byte of metadata and "p" a byte of padding.
++
++.. tabularcolumns:: |p{2.4cm}|p{1.2cm}|p{1.2cm}|p{1.2cm}|p{1.2cm}|p{.8cm}|p{.8cm}|p{.8cm}|
++
++.. flat-table::
++    :header-rows:  0
++    :stub-columns: 0
++    :widths: 12 8 8 8 8 8 8 8
++
++    * - start + 0:
++      - M\ :sub:`00`
++      - M\ :sub:`10`
++      - M\ :sub:`20`
++      - M\ :sub:`30`
++      - p
++      - p
++      - p
++    * - start + 7:
++      - M\ :sub:`01`
++      - M\ :sub:`11`
++      - M\ :sub:`21`
++      - M\ :sub:`31`
++      - p
++      - p
++      - p
++
++.. _v4l2-meta-fmt-generic-csi2-16:
++
++V4L2_META_FMT_GENERIC_CSI2_16
++-----------------------------
++
++V4L2_META_FMT_GENERIC_CSI2_16 contains packed 8-bit generic metadata, 16 bits
++for each 8 bits of data. Every byte of metadata is followed by one byte of
++padding. The way the data is stored follows the CSI-2 specification.
++
++This format is little endian.
++
++**Byte Order Of V4L2_META_FMT_GENERIC_CSI2_16.**
++Each cell is one byte. "M" denotes a byte of metadata and "p" a byte of padding.
++
++.. tabularcolumns:: |p{2.4cm}|p{1.2cm}|p{.8cm}|p{1.2cm}|p{.8cm}|p{1.2cm}|p{.8cm}|p{1.2cm}|p{.8cm}|
++
++.. flat-table::
++    :header-rows:  0
++    :stub-columns: 0
++    :widths: 12 8 8 8 8 8 8 8 8
++
++    * - start + 0:
++      - M\ :sub:`00`
++      - p
++      - M\ :sub:`10`
++      - p
++      - M\ :sub:`20`
++      - p
++      - M\ :sub:`30`
++      - p
++    * - start + 8:
++      - M\ :sub:`01`
++      - p
++      - M\ :sub:`11`
++      - p
++      - M\ :sub:`21`
++      - p
++      - M\ :sub:`31`
++      - p
++
++.. _v4l2-meta-fmt-generic-csi2-20:
++
++V4L2_META_FMT_GENERIC_CSI2_20
++-----------------------------
++
++V4L2_META_FMT_GENERIC_CSI2_20 contains packed 8-bit generic metadata, 20 bits
++for each 8 bits of data. Every byte of metadata is followed by alternating one
++and two bytes of padding. The way the data is stored follows the CSI-2
++specification.
++
++This format is little endian.
++
++**Byte Order Of V4L2_META_FMT_GENERIC_CSI2_20.**
++Each cell is one byte. "M" denotes a byte of metadata and "p" a byte of padding.
++
++.. tabularcolumns:: |p{2.4cm}|p{1.2cm}|p{.8cm}|p{1.2cm}|p{.8cm}|p{.8cm}|p{1.2cm}|p{.8cm}|p{1.2cm}|p{.8cm}|p{.8cm}|
++
++.. flat-table::
++    :header-rows:  0
++    :stub-columns: 0
++    :widths: 12 8 8 8 8 8 8 8 8 8 8
++
++    * - start + 0:
++      - M\ :sub:`00`
++      - p
++      - M\ :sub:`10`
++      - p
++      - p
++      - M\ :sub:`20`
++      - p
++      - M\ :sub:`30`
++      - p
++      - p
++    * - start + 10:
++      - M\ :sub:`01`
++      - p
++      - M\ :sub:`11`
++      - p
++      - p
++      - M\ :sub:`21`
++      - p
++      - M\ :sub:`31`
++      - p
++      - p
++
++.. _v4l2-meta-fmt-generic-csi2-24:
++
++V4L2_META_FMT_GENERIC_CSI2_24
++-----------------------------
++
++V4L2_META_FMT_GENERIC_CSI2_24 contains packed 8-bit generic metadata, 24 bits
++for each 8 bits of data. Every byte of metadata is followed by two bytes of
++padding. The way the data is stored follows the CSI-2 specification.
++
++This format is little endian.
++
++**Byte Order Of V4L2_META_FMT_GENERIC_CSI2_24.**
++Each cell is one byte. "M" denotes a byte of metadata and "p" a byte of padding.
++
++.. tabularcolumns:: |p{2.4cm}|p{1.2cm}|p{.8cm}|p{.8cm}|p{1.2cm}|p{.8cm}|p{.8cm}|p{1.2cm}|p{.8cm}|p{.8cm}|p{1.2cm}|p{.8cm}|p{.8cm}|
++
++.. flat-table::
++    :header-rows:  0
++    :stub-columns: 0
++    :widths: 12 8 8 8 8 8 8 8 8 8 8 8 8
++
++    * - start + 0:
++      - M\ :sub:`00`
++      - p
++      - p
++      - M\ :sub:`10`
++      - p
++      - p
++      - M\ :sub:`20`
++      - p
++      - p
++      - M\ :sub:`30`
++      - p
++      - p
++    * - start + 12:
++      - M\ :sub:`01`
++      - p
++      - p
++      - M\ :sub:`11`
++      - p
++      - p
++      - M\ :sub:`21`
++      - p
++      - p
++      - M\ :sub:`31`
++      - p
++      - p
++
++.. _v4l2-meta-fmt-generic-csi2-2-24:
++
++V4L2_META_FMT_GENERIC_CSI2_2_24
++-------------------------------
++
++V4L2_META_FMT_GENERIC_CSI2_2_24 contains packed 8-bit generic metadata, 24 bits
++for each two times 8 bits of data. Every two bytes of metadata are followed by
++one byte of padding. The way the data is stored follows the CSI-2
++specification.
++
++This format is little endian.
++
++**Byte Order Of V4L2_META_FMT_GENERIC_CSI2_2_24.**
++Each cell is one byte. "M" denotes a byte of metadata and "p" a byte of padding.
++
++.. tabularcolumns:: |p{2.4cm}|p{1.2cm}|p{1.2cm}|p{.8cm}|p{1.2cm}|p{1.2cm}|p{.8cm}|
++
++.. flat-table::
++    :header-rows:  0
++    :stub-columns: 0
++    :widths: 12 8 8 8 8 8 8
++
++    * - start + 0:
++      - M\ :sub:`00`
++      - M\ :sub:`10`
++      - p
++      - M\ :sub:`20`
++      - M\ :sub:`30`
++      - p
++    * - start + 6:
++      - M\ :sub:`01`
++      - M\ :sub:`11`
++      - p
++      - M\ :sub:`21`
++      - M\ :sub:`31`
++      - p
++
+diff --git a/drivers/media/v4l2-core/v4l2-ioctl.c b/drivers/media/v4l2-core/v4l2-ioctl.c
+index 33076af4dfdb..4eb3db1773e1 100644
+--- a/drivers/media/v4l2-core/v4l2-ioctl.c
++++ b/drivers/media/v4l2-core/v4l2-ioctl.c
+@@ -1452,6 +1452,14 @@ static void v4l_fill_fmtdesc(struct v4l2_fmtdesc *fmt)
+ 	case V4L2_PIX_FMT_Y210:		descr = "10-bit YUYV Packed"; break;
+ 	case V4L2_PIX_FMT_Y212:		descr = "12-bit YUYV Packed"; break;
+ 	case V4L2_PIX_FMT_Y216:		descr = "16-bit YUYV Packed"; break;
++	case V4L2_META_FMT_GENERIC_8:	descr = "8-bit Generic Metadata"; break;
++	case V4L2_META_FMT_GENERIC_CSI2_10:	descr = "8b Generic Meta, 10b CSI-2"; break;
++	case V4L2_META_FMT_GENERIC_CSI2_12:	descr = "8b Generic Meta, 12b CSI-2"; break;
++	case V4L2_META_FMT_GENERIC_CSI2_14:	descr = "8b Generic Meta, 14b CSI-2"; break;
++	case V4L2_META_FMT_GENERIC_CSI2_16:	descr = "8b Generic Meta, 16b CSI-2"; break;
++	case V4L2_META_FMT_GENERIC_CSI2_20:	descr = "8b Generic Meta, 20b CSI-2"; break;
++	case V4L2_META_FMT_GENERIC_CSI2_24:	descr = "8b Generic Meta, 24b CSI-2"; break;
++	case V4L2_META_FMT_GENERIC_CSI2_2_24:	descr = "2x8b Generic Meta, 24b CSI-2"; break;
+ 
+ 	default:
+ 		/* Compressed formats */
+diff --git a/include/uapi/linux/videodev2.h b/include/uapi/linux/videodev2.h
+index 68e7ac178cc2..2c4e03d47789 100644
+--- a/include/uapi/linux/videodev2.h
++++ b/include/uapi/linux/videodev2.h
+@@ -839,6 +839,15 @@ struct v4l2_pix_format {
+ #define V4L2_META_FMT_RK_ISP1_PARAMS	v4l2_fourcc('R', 'K', '1', 'P') /* Rockchip ISP1 3A Parameters */
+ #define V4L2_META_FMT_RK_ISP1_STAT_3A	v4l2_fourcc('R', 'K', '1', 'S') /* Rockchip ISP1 3A Statistics */
+ 
++#define V4L2_META_FMT_GENERIC_8		v4l2_fourcc('M', 'E', 'T', '8') /* Generic 8-bit metadata */
++#define V4L2_META_FMT_GENERIC_CSI2_10	v4l2_fourcc('M', 'C', '1', 'A') /* 10-bit CSI-2 packed 8-bit metadata */
++#define V4L2_META_FMT_GENERIC_CSI2_12	v4l2_fourcc('M', 'C', '1', 'C') /* 12-bit CSI-2 packed 8-bit metadata */
++#define V4L2_META_FMT_GENERIC_CSI2_14	v4l2_fourcc('M', 'C', '1', 'E') /* 14-bit CSI-2 packed 8-bit metadata */
++#define V4L2_META_FMT_GENERIC_CSI2_16	v4l2_fourcc('M', 'C', '1', 'G') /* 16-bit CSI-2 packed 8-bit metadata */
++#define V4L2_META_FMT_GENERIC_CSI2_20	v4l2_fourcc('M', 'C', '1', 'K') /* 20-bit CSI-2 packed 8-bit metadata */
++#define V4L2_META_FMT_GENERIC_CSI2_24	v4l2_fourcc('M', 'C', '1', 'O') /* 24-bit CSI-2 packed 8-bit metadata */
++#define V4L2_META_FMT_GENERIC_CSI2_2_24	v4l2_fourcc('M', 'C', '2', 'O') /* 2 bytes of 8-bit metadata, 24-bit CSI-2 packed */
++
+ /* priv field value to indicates that subsequent fields are valid. */
+ #define V4L2_PIX_FMT_PRIV_MAGIC		0xfeedcafe
+ 
+-- 
+2.43.2
+
+
+From 453627c23062ff0aa01e0e46e3b7922ddf82f998 Mon Sep 17 00:00:00 2001
+From: Sakari Ailus <sakari.ailus@linux.intel.com>
+Date: Tue, 8 Aug 2023 10:55:36 +0300
+Subject: [PATCH 06/33] media: v4l: Support line-based metadata capture
+
+many camera sensors, among other devices, transmit embedded data and image
+data for each CSI-2 frame. This embedded data typically contains register
+configuration of the sensor that has been used to capture the image data
+of the same frame.
+
+The embedded data is received by the CSI-2 receiver and has the same
+properties as the image data, including that it is line based: it has
+width, height and bytesperline (stride).
+
+Add these fields to struct v4l2_meta_format and document them.
+
+Also add V4L2_FMT_FLAG_META_LINE_BASED to tell a given format is
+line-based i.e. these fields of struct v4l2_meta_format are valid for it.
+
+Signed-off-by: Sakari Ailus <sakari.ailus@linux.intel.com>
+---
+ .../userspace-api/media/v4l/dev-meta.rst          | 15 +++++++++++++++
+ .../userspace-api/media/v4l/vidioc-enum-fmt.rst   |  7 +++++++
+ .../media/videodev2.h.rst.exceptions              |  1 +
+ drivers/media/v4l2-core/v4l2-ioctl.c              |  5 +++--
+ include/uapi/linux/videodev2.h                    | 10 ++++++++++
+ 5 files changed, 36 insertions(+), 2 deletions(-)
+
+diff --git a/Documentation/userspace-api/media/v4l/dev-meta.rst b/Documentation/userspace-api/media/v4l/dev-meta.rst
+index 0e7e1ee1471a..4b24bae6e171 100644
+--- a/Documentation/userspace-api/media/v4l/dev-meta.rst
++++ b/Documentation/userspace-api/media/v4l/dev-meta.rst
+@@ -65,3 +65,18 @@ to 0.
+       - ``buffersize``
+       - Maximum buffer size in bytes required for data. The value is set by the
+         driver.
++    * - __u32
++      - ``width``
++      - Width of a line of metadata in samples. Valid when :c:type`v4l2_fmtdesc`
++	flag ``V4L2_FMT_FLAG_META_LINE_BASED`` is set, otherwise zero. See
++	:c:func:`VIDIOC_ENUM_FMT`.
++    * - __u32
++      - ``height``
++      - Number of rows of metadata. Valid when :c:type`v4l2_fmtdesc` flag
++	``V4L2_FMT_FLAG_META_LINE_BASED`` is set, otherwise zero. See
++	:c:func:`VIDIOC_ENUM_FMT`.
++    * - __u32
++      - ``bytesperline``
++      - Offset in bytes between the beginning of two consecutive lines. Valid
++	when :c:type`v4l2_fmtdesc` flag ``V4L2_FMT_FLAG_META_LINE_BASED`` is
++	set, otherwise zero. See :c:func:`VIDIOC_ENUM_FMT`.
+diff --git a/Documentation/userspace-api/media/v4l/vidioc-enum-fmt.rst b/Documentation/userspace-api/media/v4l/vidioc-enum-fmt.rst
+index 000c154b0f98..6d7664345a4e 100644
+--- a/Documentation/userspace-api/media/v4l/vidioc-enum-fmt.rst
++++ b/Documentation/userspace-api/media/v4l/vidioc-enum-fmt.rst
+@@ -227,6 +227,13 @@ the ``mbus_code`` field is handled differently:
+ 	The application can ask to configure the quantization of the capture
+ 	device when calling the :ref:`VIDIOC_S_FMT <VIDIOC_G_FMT>` ioctl with
+ 	:ref:`V4L2_PIX_FMT_FLAG_SET_CSC <v4l2-pix-fmt-flag-set-csc>` set.
++    * - ``V4L2_FMT_FLAG_META_LINE_BASED``
++      - 0x0200
++      - The metadata format is line-based. In this case the ``width``,
++	``height`` and ``bytesperline`` fields of :c:type:`v4l2_meta_format` are
++	valid. The buffer consists of ``height`` lines, each having ``width``
++	bytes of data and offset between the beginning of each two consecutive
++	lines is ``bytesperline``.
+ 
+ Return Value
+ ============
+diff --git a/Documentation/userspace-api/media/videodev2.h.rst.exceptions b/Documentation/userspace-api/media/videodev2.h.rst.exceptions
+index 3e58aac4ef0b..bdc628e8c1d6 100644
+--- a/Documentation/userspace-api/media/videodev2.h.rst.exceptions
++++ b/Documentation/userspace-api/media/videodev2.h.rst.exceptions
+@@ -215,6 +215,7 @@ replace define V4L2_FMT_FLAG_CSC_XFER_FUNC fmtdesc-flags
+ replace define V4L2_FMT_FLAG_CSC_YCBCR_ENC fmtdesc-flags
+ replace define V4L2_FMT_FLAG_CSC_HSV_ENC fmtdesc-flags
+ replace define V4L2_FMT_FLAG_CSC_QUANTIZATION fmtdesc-flags
++replace define V4L2_FMT_FLAG_META_LINE_BASED fmtdesc-flags
+ 
+ # V4L2 timecode types
+ replace define V4L2_TC_TYPE_24FPS timecode-type
+diff --git a/drivers/media/v4l2-core/v4l2-ioctl.c b/drivers/media/v4l2-core/v4l2-ioctl.c
+index 4eb3db1773e1..1fcec1515bcc 100644
+--- a/drivers/media/v4l2-core/v4l2-ioctl.c
++++ b/drivers/media/v4l2-core/v4l2-ioctl.c
+@@ -343,8 +343,9 @@ static void v4l_print_format(const void *arg, bool write_only)
+ 	case V4L2_BUF_TYPE_META_OUTPUT:
+ 		meta = &p->fmt.meta;
+ 		pixelformat = meta->dataformat;
+-		pr_cont(", dataformat=%p4cc, buffersize=%u\n",
+-			&pixelformat, meta->buffersize);
++		pr_cont(", dataformat=%p4cc, buffersize=%u, width=%u, height=%u, bytesperline=%u\n",
++			&pixelformat, meta->buffersize, meta->width,
++			meta->height, meta->bytesperline);
+ 		break;
+ 	}
+ }
+diff --git a/include/uapi/linux/videodev2.h b/include/uapi/linux/videodev2.h
+index 2c4e03d47789..48fb44772098 100644
+--- a/include/uapi/linux/videodev2.h
++++ b/include/uapi/linux/videodev2.h
+@@ -878,6 +878,7 @@ struct v4l2_fmtdesc {
+ #define V4L2_FMT_FLAG_CSC_YCBCR_ENC		0x0080
+ #define V4L2_FMT_FLAG_CSC_HSV_ENC		V4L2_FMT_FLAG_CSC_YCBCR_ENC
+ #define V4L2_FMT_FLAG_CSC_QUANTIZATION		0x0100
++#define V4L2_FMT_FLAG_META_LINE_BASED		0x0200
+ 
+ 	/* Frame Size and frame rate enumeration */
+ /*
+@@ -2424,10 +2425,19 @@ struct v4l2_sdr_format {
+  * struct v4l2_meta_format - metadata format definition
+  * @dataformat:		little endian four character code (fourcc)
+  * @buffersize:		maximum size in bytes required for data
++ * @width:		number of bytes of data per line (valid for line based
++ *			formats only, see format documentation)
++ * @height:		number of lines of data per buffer (valid for line based
++ *			formats only)
++ * @bytesperline:	offset between the beginnings of two adjacent lines in
++ *			bytes (valid for line based formats only)
+  */
+ struct v4l2_meta_format {
+ 	__u32				dataformat;
+ 	__u32				buffersize;
++	__u32				width;
++	__u32				height;
++	__u32				bytesperline;
+ } __attribute__ ((packed));
+ 
+ /**
+-- 
+2.43.2
+
+
+From 090bb3894bda739ff06e8a431815210b731056b7 Mon Sep 17 00:00:00 2001
+From: Sakari Ailus <sakari.ailus@linux.intel.com>
+Date: Tue, 8 Aug 2023 10:55:37 +0300
+Subject: [PATCH 07/33] media: Add media bus codes for MIPI CCS embedded data
+
+Add new MIPI CCS embedded data media bus formats.
+
+Signed-off-by: Sakari Ailus <sakari.ailus@linux.intel.com>
+---
+ .../media/v4l/subdev-formats.rst              | 32 +++++++++++++++++++
+ include/uapi/linux/media-bus-format.h         | 10 +++++-
+ 2 files changed, 41 insertions(+), 1 deletion(-)
+
+diff --git a/Documentation/userspace-api/media/v4l/subdev-formats.rst b/Documentation/userspace-api/media/v4l/subdev-formats.rst
+index 7d107873cddd..7afb057a09c5 100644
+--- a/Documentation/userspace-api/media/v4l/subdev-formats.rst
++++ b/Documentation/userspace-api/media/v4l/subdev-formats.rst
+@@ -8563,3 +8563,35 @@ and finally the bit number in subscript. "p" indicates a padding bit.
+       - p
+       - p
+       - p
++
++MIPI CCS Embedded Data Formats
++^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
++
++`MIPI CCS <https://www.mipi.org/specifications/camera-command-set>`_ defines an
++metadata format for sensor embedded data, which is used to store the register
++configuration used for capturing a given frame. The format is defined in the CCS
++specification.
++
++The bit depth of the CCS embedded data matches the pixel data bit depth
++configured on the sensor. The formats used and their corresponding generic
++formats are listed in the table below.
++
++.. flat-table: CCS embedded data mbus formats and corresponding generic formats
++    :header-rows: 1
++
++    * - CCS embedded data mbus format
++      - Generic metadata format
++    * - MEDIA_BUS_FMT_CCS_EMBEDDED_8
++      - MEDIA_BUS_FMT_META_8
++    * - MEDIA_BUS_FMT_CCS_EMBEDDED_10
++      - MEDIA_BUS_FMT_META_10
++    * - MEDIA_BUS_FMT_CCS_EMBEDDED_12
++      - MEDIA_BUS_FMT_META_12
++    * - MEDIA_BUS_FMT_CCS_EMBEDDED_14
++      - MEDIA_BUS_FMT_META_14
++    * - MEDIA_BUS_FMT_CCS_EMBEDDED_16
++      - MEDIA_BUS_FMT_META_16
++    * - MEDIA_BUS_FMT_CCS_EMBEDDED_20
++      - MEDIA_BUS_FMT_META_20
++    * - MEDIA_BUS_FMT_CCS_EMBEDDED_24
++      - MEDIA_BUS_FMT_META_24
+diff --git a/include/uapi/linux/media-bus-format.h b/include/uapi/linux/media-bus-format.h
+index 6fcd7d276bc6..d5014ef3c43a 100644
+--- a/include/uapi/linux/media-bus-format.h
++++ b/include/uapi/linux/media-bus-format.h
+@@ -183,7 +183,15 @@
+ #define MEDIA_BUS_FMT_META_20			0x8006
+ #define MEDIA_BUS_FMT_META_24			0x8007
+ 
++/* Specific metadata formats. Next is 0x9008. */
++#define MEDIA_BUS_FMT_CCS_EMBEDDED_8		0x9001
++#define MEDIA_BUS_FMT_CCS_EMBEDDED_10		0x9002
++#define MEDIA_BUS_FMT_CCS_EMBEDDED_12		0x9003
++#define MEDIA_BUS_FMT_CCS_EMBEDDED_14		0x9004
++#define MEDIA_BUS_FMT_CCS_EMBEDDED_16		0x9005
++#define MEDIA_BUS_FMT_CCS_EMBEDDED_20		0x9006
++#define MEDIA_BUS_FMT_CCS_EMBEDDED_24		0x9007
++
+ #define MEDIA_BUS_FMT_IS_META(code)		\
+ 	((code) & 0xf000 == 0x7000 || (code) & 0xf000 == 0x8000)
+-
+ #endif /* __LINUX_MEDIA_BUS_FORMAT_H */
+-- 
+2.43.2
+
+
+From 44f8084f055969874d2216ba4e6e225046931e73 Mon Sep 17 00:00:00 2001
+From: Bingbu Cao <bingbu.cao@intel.com>
+Date: Thu, 11 Jan 2024 14:55:15 +0800
+Subject: [PATCH 08/33] media: intel/ipu6: add Intel IPU6 PCI device driver
+
+Intel Image Processing Unit 6th Gen includes input and processing systems
+but the hardware presents itself as a single PCI device in system.
+
+IPU6 PCI device driver basically does PCI configurations and load
+the firmware binary, initialises IPU virtual bus, and sets up platform
+specific variants to support multiple IPU6 devices in single device
+driver.
+
+Signed-off-by: Bingbu Cao <bingbu.cao@intel.com>
+---
+ .../media/pci/intel/ipu6/ipu6-platform-regs.h | 179 ++++
+ drivers/media/pci/intel/ipu6/ipu6.c           | 966 ++++++++++++++++++
+ drivers/media/pci/intel/ipu6/ipu6.h           | 356 +++++++
+ 3 files changed, 1501 insertions(+)
+ create mode 100644 drivers/media/pci/intel/ipu6/ipu6-platform-regs.h
+ create mode 100644 drivers/media/pci/intel/ipu6/ipu6.c
+ create mode 100644 drivers/media/pci/intel/ipu6/ipu6.h
+
+diff --git a/drivers/media/pci/intel/ipu6/ipu6-platform-regs.h b/drivers/media/pci/intel/ipu6/ipu6-platform-regs.h
+new file mode 100644
+index 000000000000..cae26009c9fc
+--- /dev/null
++++ b/drivers/media/pci/intel/ipu6/ipu6-platform-regs.h
+@@ -0,0 +1,179 @@
++/* SPDX-License-Identifier: GPL-2.0-only */
++/* Copyright (C) 2018 - 2023 Intel Corporation */
++
++#ifndef IPU6_PLATFORM_REGS_H
++#define IPU6_PLATFORM_REGS_H
++
++#include <linux/bits.h>
++
++/*
++ * IPU6 uses uniform address within IPU6, therefore all subsystem registers
++ * locates in one single space starts from 0 but in different sctions with
++ * different addresses, the subsystem offsets are defined to 0 as the
++ * register definition will have the address offset to 0.
++ */
++#define IPU6_UNIFIED_OFFSET			0
++
++#define IPU6_ISYS_IOMMU0_OFFSET		0x2e0000
++#define IPU6_ISYS_IOMMU1_OFFSET		0x2e0500
++#define IPU6_ISYS_IOMMUI_OFFSET		0x2e0a00
++
++#define IPU6_PSYS_IOMMU0_OFFSET		0x1b0000
++#define IPU6_PSYS_IOMMU1_OFFSET		0x1b0700
++#define IPU6_PSYS_IOMMU1R_OFFSET	0x1b0e00
++#define IPU6_PSYS_IOMMUI_OFFSET		0x1b1500
++
++/* the offset from IOMMU base register */
++#define IPU6_MMU_L1_STREAM_ID_REG_OFFSET	0x0c
++#define IPU6_MMU_L2_STREAM_ID_REG_OFFSET	0x4c
++#define IPU6_PSYS_MMU1W_L2_STREAM_ID_REG_OFFSET	0x8c
++
++#define IPU6_MMU_INFO_OFFSET		0x8
++
++#define IPU6_ISYS_SPC_OFFSET		0x210000
++
++#define IPU6SE_PSYS_SPC_OFFSET		0x110000
++#define IPU6_PSYS_SPC_OFFSET		0x118000
++
++#define IPU6_ISYS_DMEM_OFFSET		0x200000
++#define IPU6_PSYS_DMEM_OFFSET		0x100000
++
++#define IPU6_REG_ISYS_UNISPART_IRQ_EDGE			0x27c000
++#define IPU6_REG_ISYS_UNISPART_IRQ_MASK			0x27c004
++#define IPU6_REG_ISYS_UNISPART_IRQ_STATUS		0x27c008
++#define IPU6_REG_ISYS_UNISPART_IRQ_CLEAR		0x27c00c
++#define IPU6_REG_ISYS_UNISPART_IRQ_ENABLE		0x27c010
++#define IPU6_REG_ISYS_UNISPART_IRQ_LEVEL_NOT_PULSE	0x27c014
++#define IPU6_REG_ISYS_UNISPART_SW_IRQ_REG		0x27c414
++#define IPU6_REG_ISYS_UNISPART_SW_IRQ_MUX_REG		0x27c418
++#define IPU6_ISYS_UNISPART_IRQ_CSI0			BIT(2)
++#define IPU6_ISYS_UNISPART_IRQ_CSI1			BIT(3)
++#define IPU6_ISYS_UNISPART_IRQ_SW			BIT(22)
++
++#define IPU6_REG_ISYS_ISL_TOP_IRQ_EDGE			0x2b0200
++#define IPU6_REG_ISYS_ISL_TOP_IRQ_MASK			0x2b0204
++#define IPU6_REG_ISYS_ISL_TOP_IRQ_STATUS		0x2b0208
++#define IPU6_REG_ISYS_ISL_TOP_IRQ_CLEAR			0x2b020c
++#define IPU6_REG_ISYS_ISL_TOP_IRQ_ENABLE		0x2b0210
++#define IPU6_REG_ISYS_ISL_TOP_IRQ_LEVEL_NOT_PULSE	0x2b0214
++
++#define IPU6_REG_ISYS_CMPR_TOP_IRQ_EDGE			0x2d2100
++#define IPU6_REG_ISYS_CMPR_TOP_IRQ_MASK			0x2d2104
++#define IPU6_REG_ISYS_CMPR_TOP_IRQ_STATUS		0x2d2108
++#define IPU6_REG_ISYS_CMPR_TOP_IRQ_CLEAR		0x2d210c
++#define IPU6_REG_ISYS_CMPR_TOP_IRQ_ENABLE		0x2d2110
++#define IPU6_REG_ISYS_CMPR_TOP_IRQ_LEVEL_NOT_PULSE	0x2d2114
++
++/* CDC Burst collector thresholds for isys - 3 FIFOs i = 0..2 */
++#define IPU6_REG_ISYS_CDC_THRESHOLD(i)		(0x27c400 + ((i) * 4))
++
++#define IPU6_CSI_IRQ_NUM_PER_PIPE			4
++#define IPU6SE_ISYS_CSI_PORT_NUM			4
++#define IPU6_ISYS_CSI_PORT_NUM				8
++
++#define IPU6_ISYS_CSI_PORT_IRQ(irq_num)		BIT(irq_num)
++
++/* PKG DIR OFFSET in IMR in secure mode */
++#define IPU6_PKG_DIR_IMR_OFFSET			0x40
++
++#define IPU6_ISYS_REG_SPC_STATUS_CTRL		0x0
++
++#define IPU6_ISYS_SPC_STATUS_START			BIT(1)
++#define IPU6_ISYS_SPC_STATUS_RUN			BIT(3)
++#define IPU6_ISYS_SPC_STATUS_READY			BIT(5)
++#define IPU6_ISYS_SPC_STATUS_CTRL_ICACHE_INVALIDATE	BIT(12)
++#define IPU6_ISYS_SPC_STATUS_ICACHE_PREFETCH		BIT(13)
++
++#define IPU6_PSYS_REG_SPC_STATUS_CTRL			0x0
++#define IPU6_PSYS_REG_SPC_START_PC			0x4
++#define IPU6_PSYS_REG_SPC_ICACHE_BASE			0x10
++#define IPU6_REG_PSYS_INFO_SEG_0_CONFIG_ICACHE_MASTER	0x14
++
++#define IPU6_PSYS_SPC_STATUS_START			BIT(1)
++#define IPU6_PSYS_SPC_STATUS_RUN			BIT(3)
++#define IPU6_PSYS_SPC_STATUS_READY			BIT(5)
++#define IPU6_PSYS_SPC_STATUS_CTRL_ICACHE_INVALIDATE	BIT(12)
++#define IPU6_PSYS_SPC_STATUS_ICACHE_PREFETCH		BIT(13)
++
++#define IPU6_PSYS_REG_SPP0_STATUS_CTRL			0x20000
++
++#define IPU6_INFO_ENABLE_SNOOP			BIT(0)
++#define IPU6_INFO_DEC_FORCE_FLUSH		BIT(1)
++#define IPU6_INFO_DEC_PASS_THROUGH		BIT(2)
++#define IPU6_INFO_ZLW				BIT(3)
++#define IPU6_INFO_REQUEST_DESTINATION_IOSF	BIT(9)
++#define IPU6_INFO_IMR_BASE			BIT(10)
++#define IPU6_INFO_IMR_DESTINED			BIT(11)
++
++#define IPU6_INFO_REQUEST_DESTINATION_PRIMARY IPU6_INFO_REQUEST_DESTINATION_IOSF
++
++/*
++ * s2m_pixel_soc_pixel_remapping is dedicated for the enabling of the
++ * pixel s2m remp ability.Remap here  means that s2m rearange the order
++ * of the pixels in each 4 pixels group.
++ * For examle, mirroring remping means that if input's 4 first pixels
++ * are 1 2 3 4 then in output we should see 4 3 2 1 in this 4 first pixels.
++ * 0xE4 is from s2m MAS document. It means no remapping.
++ */
++#define S2M_PIXEL_SOC_PIXEL_REMAPPING_FLAG_NO_REMAPPING 0xe4
++/*
++ * csi_be_soc_pixel_remapping is for the enabling of the pixel remapping.
++ * This remapping is exactly like the stream2mmio remapping.
++ */
++#define CSI_BE_SOC_PIXEL_REMAPPING_FLAG_NO_REMAPPING    0xe4
++
++#define IPU6_REG_DMA_TOP_AB_GROUP1_BASE_ADDR		0x1ae000
++#define IPU6_REG_DMA_TOP_AB_GROUP2_BASE_ADDR		0x1af000
++#define IPU6_REG_DMA_TOP_AB_RING_MIN_OFFSET(n)		(0x4 + (n) * 0xc)
++#define IPU6_REG_DMA_TOP_AB_RING_MAX_OFFSET(n)		(0x8 + (n) * 0xc)
++#define IPU6_REG_DMA_TOP_AB_RING_ACCESS_OFFSET(n)	(0xc + (n) * 0xc)
++
++enum ipu6_device_ab_group1_target_id {
++	IPU6_DEVICE_AB_GROUP1_TARGET_ID_R0_SPC_DMEM,
++	IPU6_DEVICE_AB_GROUP1_TARGET_ID_R1_SPC_DMEM,
++	IPU6_DEVICE_AB_GROUP1_TARGET_ID_R2_SPC_DMEM,
++	IPU6_DEVICE_AB_GROUP1_TARGET_ID_R3_SPC_STATUS_REG,
++	IPU6_DEVICE_AB_GROUP1_TARGET_ID_R4_SPC_MASTER_BASE_ADDR,
++	IPU6_DEVICE_AB_GROUP1_TARGET_ID_R5_SPC_PC_STALL,
++	IPU6_DEVICE_AB_GROUP1_TARGET_ID_R6_SPC_EQ,
++	IPU6_DEVICE_AB_GROUP1_TARGET_ID_R7_SPC_RESERVED,
++	IPU6_DEVICE_AB_GROUP1_TARGET_ID_R8_SPC_RESERVED,
++	IPU6_DEVICE_AB_GROUP1_TARGET_ID_R9_SPP0,
++	IPU6_DEVICE_AB_GROUP1_TARGET_ID_R10_SPP1,
++	IPU6_DEVICE_AB_GROUP1_TARGET_ID_R11_CENTRAL_R1,
++	IPU6_DEVICE_AB_GROUP1_TARGET_ID_R12_IRQ,
++	IPU6_DEVICE_AB_GROUP1_TARGET_ID_R13_CENTRAL_R2,
++	IPU6_DEVICE_AB_GROUP1_TARGET_ID_R14_DMA,
++	IPU6_DEVICE_AB_GROUP1_TARGET_ID_R15_DMA,
++	IPU6_DEVICE_AB_GROUP1_TARGET_ID_R16_GP,
++	IPU6_DEVICE_AB_GROUP1_TARGET_ID_R17_ZLW_INSERTER,
++	IPU6_DEVICE_AB_GROUP1_TARGET_ID_R18_AB,
++};
++
++enum nci_ab_access_mode {
++	NCI_AB_ACCESS_MODE_RW,	/* read & write */
++	NCI_AB_ACCESS_MODE_RO,	/* read only */
++	NCI_AB_ACCESS_MODE_WO,	/* write only */
++	NCI_AB_ACCESS_MODE_NA,	/* No access at all */
++};
++
++/* IRQ-related registers in PSYS */
++#define IPU6_REG_PSYS_GPDEV_IRQ_EDGE		0x1aa200
++#define IPU6_REG_PSYS_GPDEV_IRQ_MASK		0x1aa204
++#define IPU6_REG_PSYS_GPDEV_IRQ_STATUS		0x1aa208
++#define IPU6_REG_PSYS_GPDEV_IRQ_CLEAR		0x1aa20c
++#define IPU6_REG_PSYS_GPDEV_IRQ_ENABLE		0x1aa210
++#define IPU6_REG_PSYS_GPDEV_IRQ_LEVEL_NOT_PULSE	0x1aa214
++/* There are 8 FW interrupts, n = 0..7 */
++#define IPU6_PSYS_GPDEV_FWIRQ0			5
++#define IPU6_PSYS_GPDEV_FWIRQ1			6
++#define IPU6_PSYS_GPDEV_FWIRQ2			7
++#define IPU6_PSYS_GPDEV_FWIRQ3			8
++#define IPU6_PSYS_GPDEV_FWIRQ4			9
++#define IPU6_PSYS_GPDEV_FWIRQ5			10
++#define IPU6_PSYS_GPDEV_FWIRQ6			11
++#define IPU6_PSYS_GPDEV_FWIRQ7			12
++#define IPU6_PSYS_GPDEV_IRQ_FWIRQ(n)		BIT(n)
++#define IPU6_REG_PSYS_GPDEV_FWIRQ(n)		(4 * (n) + 0x1aa100)
++
++#endif /* IPU6_PLATFORM_REGS_H */
+diff --git a/drivers/media/pci/intel/ipu6/ipu6.c b/drivers/media/pci/intel/ipu6/ipu6.c
+new file mode 100644
+index 000000000000..abd40a783729
+--- /dev/null
++++ b/drivers/media/pci/intel/ipu6/ipu6.c
+@@ -0,0 +1,966 @@
++// SPDX-License-Identifier: GPL-2.0-only
++/*
++ * Copyright (C) 2013 - 2023 Intel Corporation
++ */
++
++#include <linux/bitfield.h>
++#include <linux/bits.h>
++#include <linux/dma-mapping.h>
++#include <linux/err.h>
++#include <linux/firmware.h>
++#include <linux/kernel.h>
++#include <linux/interrupt.h>
++#include <linux/io.h>
++#include <linux/list.h>
++#include <linux/module.h>
++#include <linux/pci-ats.h>
++#include <linux/pm_runtime.h>
++#include <linux/property.h>
++#include <linux/scatterlist.h>
++#include <linux/slab.h>
++#include <linux/types.h>
++
++#include <media/ipu-bridge.h>
++
++#include "ipu6.h"
++#include "ipu6-bus.h"
++#include "ipu6-buttress.h"
++#include "ipu6-cpd.h"
++#include "ipu6-isys.h"
++#include "ipu6-mmu.h"
++#include "ipu6-platform-buttress-regs.h"
++#include "ipu6-platform-isys-csi2-reg.h"
++#include "ipu6-platform-regs.h"
++
++#define IPU6_PCI_BAR		0
++
++struct ipu6_cell_program {
++	u32 magic_number;
++
++	u32 blob_offset;
++	u32 blob_size;
++
++	u32 start[3];
++
++	u32 icache_source;
++	u32 icache_target;
++	u32 icache_size;
++
++	u32 pmem_source;
++	u32 pmem_target;
++	u32 pmem_size;
++
++	u32 data_source;
++	u32 data_target;
++	u32 data_size;
++
++	u32 bss_target;
++	u32 bss_size;
++
++	u32 cell_id;
++	u32 regs_addr;
++
++	u32 cell_pmem_data_bus_address;
++	u32 cell_dmem_data_bus_address;
++	u32 cell_pmem_control_bus_address;
++	u32 cell_dmem_control_bus_address;
++
++	u32 next;
++	u32 dummy[2];
++};
++
++static u32 ipu6se_csi_offsets[] = {
++	IPU6_CSI_PORT_A_ADDR_OFFSET,
++	IPU6_CSI_PORT_B_ADDR_OFFSET,
++	IPU6_CSI_PORT_C_ADDR_OFFSET,
++	IPU6_CSI_PORT_D_ADDR_OFFSET
++};
++
++/*
++ * IPU6 on TGL support maximum 8 csi2 ports
++ * IPU6SE on JSL and IPU6EP on ADL support maximum 4 csi2 ports
++ * IPU6EP on MTL support maximum 6 csi2 ports
++ */
++static u32 ipu6_tgl_csi_offsets[] = {
++	IPU6_CSI_PORT_A_ADDR_OFFSET,
++	IPU6_CSI_PORT_B_ADDR_OFFSET,
++	IPU6_CSI_PORT_C_ADDR_OFFSET,
++	IPU6_CSI_PORT_D_ADDR_OFFSET,
++	IPU6_CSI_PORT_E_ADDR_OFFSET,
++	IPU6_CSI_PORT_F_ADDR_OFFSET,
++	IPU6_CSI_PORT_G_ADDR_OFFSET,
++	IPU6_CSI_PORT_H_ADDR_OFFSET
++};
++
++static u32 ipu6ep_mtl_csi_offsets[] = {
++	IPU6_CSI_PORT_A_ADDR_OFFSET,
++	IPU6_CSI_PORT_B_ADDR_OFFSET,
++	IPU6_CSI_PORT_C_ADDR_OFFSET,
++	IPU6_CSI_PORT_D_ADDR_OFFSET,
++	IPU6_CSI_PORT_E_ADDR_OFFSET,
++	IPU6_CSI_PORT_F_ADDR_OFFSET
++};
++
++static u32 ipu6_csi_offsets[] = {
++	IPU6_CSI_PORT_A_ADDR_OFFSET,
++	IPU6_CSI_PORT_B_ADDR_OFFSET,
++	IPU6_CSI_PORT_C_ADDR_OFFSET,
++	IPU6_CSI_PORT_D_ADDR_OFFSET
++};
++
++static struct ipu6_isys_internal_pdata isys_ipdata = {
++	.hw_variant = {
++		.offset = IPU6_UNIFIED_OFFSET,
++		.nr_mmus = 3,
++		.mmu_hw = {
++			{
++				.offset = IPU6_ISYS_IOMMU0_OFFSET,
++				.info_bits = IPU6_INFO_REQUEST_DESTINATION_IOSF,
++				.nr_l1streams = 16,
++				.l1_block_sz = {
++					3, 8, 2, 2, 2, 2, 2, 2, 1, 1,
++					1, 1, 1, 1, 1, 1
++				},
++				.nr_l2streams = 16,
++				.l2_block_sz = {
++					2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
++					2, 2, 2, 2, 2, 2
++				},
++				.insert_read_before_invalidate = false,
++				.l1_stream_id_reg_offset =
++				IPU6_MMU_L1_STREAM_ID_REG_OFFSET,
++				.l2_stream_id_reg_offset =
++				IPU6_MMU_L2_STREAM_ID_REG_OFFSET,
++			},
++			{
++				.offset = IPU6_ISYS_IOMMU1_OFFSET,
++				.info_bits = 0,
++				.nr_l1streams = 16,
++				.l1_block_sz = {
++					2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
++					2, 2, 2, 1, 1, 4
++				},
++				.nr_l2streams = 16,
++				.l2_block_sz = {
++					2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
++					2, 2, 2, 2, 2, 2
++				},
++				.insert_read_before_invalidate = false,
++				.l1_stream_id_reg_offset =
++				IPU6_MMU_L1_STREAM_ID_REG_OFFSET,
++				.l2_stream_id_reg_offset =
++				IPU6_MMU_L2_STREAM_ID_REG_OFFSET,
++			},
++			{
++				.offset = IPU6_ISYS_IOMMUI_OFFSET,
++				.info_bits = 0,
++				.nr_l1streams = 0,
++				.nr_l2streams = 0,
++				.insert_read_before_invalidate = false,
++			},
++		},
++		.cdc_fifos = 3,
++		.cdc_fifo_threshold = {6, 8, 2},
++		.dmem_offset = IPU6_ISYS_DMEM_OFFSET,
++		.spc_offset = IPU6_ISYS_SPC_OFFSET,
++	},
++	.isys_dma_overshoot = IPU6_ISYS_OVERALLOC_MIN,
++};
++
++static struct ipu6_psys_internal_pdata psys_ipdata = {
++	.hw_variant = {
++		.offset = IPU6_UNIFIED_OFFSET,
++		.nr_mmus = 4,
++		.mmu_hw = {
++			{
++				.offset = IPU6_PSYS_IOMMU0_OFFSET,
++				.info_bits =
++				IPU6_INFO_REQUEST_DESTINATION_IOSF,
++				.nr_l1streams = 16,
++				.l1_block_sz = {
++					2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
++					2, 2, 2, 2, 2, 2
++				},
++				.nr_l2streams = 16,
++				.l2_block_sz = {
++					2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
++					2, 2, 2, 2, 2, 2
++				},
++				.insert_read_before_invalidate = false,
++				.l1_stream_id_reg_offset =
++				IPU6_MMU_L1_STREAM_ID_REG_OFFSET,
++				.l2_stream_id_reg_offset =
++				IPU6_MMU_L2_STREAM_ID_REG_OFFSET,
++			},
++			{
++				.offset = IPU6_PSYS_IOMMU1_OFFSET,
++				.info_bits = 0,
++				.nr_l1streams = 32,
++				.l1_block_sz = {
++					1, 2, 2, 2, 2, 2, 2, 2, 2, 2,
++					2, 2, 2, 2, 2, 10,
++					5, 4, 14, 6, 4, 14, 6, 4, 8,
++					4, 2, 1, 1, 1, 1, 14
++				},
++				.nr_l2streams = 32,
++				.l2_block_sz = {
++					2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
++					2, 2, 2, 2, 2, 2,
++					2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
++					2, 2, 2, 2, 2, 2
++				},
++				.insert_read_before_invalidate = false,
++				.l1_stream_id_reg_offset =
++				IPU6_MMU_L1_STREAM_ID_REG_OFFSET,
++				.l2_stream_id_reg_offset =
++				IPU6_PSYS_MMU1W_L2_STREAM_ID_REG_OFFSET,
++			},
++			{
++				.offset = IPU6_PSYS_IOMMU1R_OFFSET,
++				.info_bits = 0,
++				.nr_l1streams = 16,
++				.l1_block_sz = {
++					1, 4, 4, 4, 4, 16, 8, 4, 32,
++					16, 16, 2, 2, 2, 1, 12
++				},
++				.nr_l2streams = 16,
++				.l2_block_sz = {
++					2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
++					2, 2, 2, 2, 2, 2
++				},
++				.insert_read_before_invalidate = false,
++				.l1_stream_id_reg_offset =
++				IPU6_MMU_L1_STREAM_ID_REG_OFFSET,
++				.l2_stream_id_reg_offset =
++				IPU6_MMU_L2_STREAM_ID_REG_OFFSET,
++			},
++			{
++				.offset = IPU6_PSYS_IOMMUI_OFFSET,
++				.info_bits = 0,
++				.nr_l1streams = 0,
++				.nr_l2streams = 0,
++				.insert_read_before_invalidate = false,
++			},
++		},
++		.dmem_offset = IPU6_PSYS_DMEM_OFFSET,
++	},
++};
++
++static const struct ipu6_buttress_ctrl isys_buttress_ctrl = {
++	.ratio = IPU6_IS_FREQ_CTL_DEFAULT_RATIO,
++	.qos_floor = IPU6_IS_FREQ_CTL_DEFAULT_QOS_FLOOR_RATIO,
++	.freq_ctl = IPU6_BUTTRESS_REG_IS_FREQ_CTL,
++	.pwr_sts_shift = IPU6_BUTTRESS_PWR_STATE_IS_PWR_SHIFT,
++	.pwr_sts_mask = IPU6_BUTTRESS_PWR_STATE_IS_PWR_MASK,
++	.pwr_sts_on = IPU6_BUTTRESS_PWR_STATE_UP_DONE,
++	.pwr_sts_off = IPU6_BUTTRESS_PWR_STATE_DN_DONE,
++};
++
++static const struct ipu6_buttress_ctrl psys_buttress_ctrl = {
++	.ratio = IPU6_PS_FREQ_CTL_DEFAULT_RATIO,
++	.qos_floor = IPU6_PS_FREQ_CTL_DEFAULT_QOS_FLOOR_RATIO,
++	.freq_ctl = IPU6_BUTTRESS_REG_PS_FREQ_CTL,
++	.pwr_sts_shift = IPU6_BUTTRESS_PWR_STATE_PS_PWR_SHIFT,
++	.pwr_sts_mask = IPU6_BUTTRESS_PWR_STATE_PS_PWR_MASK,
++	.pwr_sts_on = IPU6_BUTTRESS_PWR_STATE_UP_DONE,
++	.pwr_sts_off = IPU6_BUTTRESS_PWR_STATE_DN_DONE,
++};
++
++static void
++ipu6_pkg_dir_configure_spc(struct ipu6_device *isp,
++			   const struct ipu6_hw_variants *hw_variant,
++			   int pkg_dir_idx, void __iomem *base,
++			   u64 *pkg_dir, dma_addr_t pkg_dir_vied_address)
++{
++	struct ipu6_cell_program *prog;
++	void __iomem *spc_base;
++	u32 server_fw_addr;
++	dma_addr_t dma_addr;
++	u32 pg_offset;
++
++	server_fw_addr = lower_32_bits(*(pkg_dir + (pkg_dir_idx + 1) * 2));
++	if (pkg_dir_idx == IPU6_CPD_PKG_DIR_ISYS_SERVER_IDX)
++		dma_addr = sg_dma_address(isp->isys->fw_sgt.sgl);
++	else
++		dma_addr = sg_dma_address(isp->psys->fw_sgt.sgl);
++
++	pg_offset = server_fw_addr - dma_addr;
++	prog = (struct ipu6_cell_program *)((u64)isp->cpd_fw->data + pg_offset);
++	spc_base = base + prog->regs_addr;
++	if (spc_base != (base + hw_variant->spc_offset))
++		dev_warn(&isp->pdev->dev,
++			 "SPC reg addr %p not matching value from CPD %p\n",
++			 base + hw_variant->spc_offset, spc_base);
++	writel(server_fw_addr + prog->blob_offset +
++	       prog->icache_source, spc_base + IPU6_PSYS_REG_SPC_ICACHE_BASE);
++	writel(IPU6_INFO_REQUEST_DESTINATION_IOSF,
++	       spc_base + IPU6_REG_PSYS_INFO_SEG_0_CONFIG_ICACHE_MASTER);
++	writel(prog->start[1], spc_base + IPU6_PSYS_REG_SPC_START_PC);
++	writel(pkg_dir_vied_address, base + hw_variant->dmem_offset);
++}
++
++void ipu6_configure_spc(struct ipu6_device *isp,
++			const struct ipu6_hw_variants *hw_variant,
++			int pkg_dir_idx, void __iomem *base, u64 *pkg_dir,
++			dma_addr_t pkg_dir_dma_addr)
++{
++	void __iomem *dmem_base = base + hw_variant->dmem_offset;
++	void __iomem *spc_regs_base = base + hw_variant->spc_offset;
++	u32 val;
++
++	val = readl(spc_regs_base + IPU6_PSYS_REG_SPC_STATUS_CTRL);
++	val |= IPU6_PSYS_SPC_STATUS_CTRL_ICACHE_INVALIDATE;
++	writel(val, spc_regs_base + IPU6_PSYS_REG_SPC_STATUS_CTRL);
++
++	if (isp->secure_mode)
++		writel(IPU6_PKG_DIR_IMR_OFFSET, dmem_base);
++	else
++		ipu6_pkg_dir_configure_spc(isp, hw_variant, pkg_dir_idx, base,
++					   pkg_dir, pkg_dir_dma_addr);
++}
++EXPORT_SYMBOL_NS_GPL(ipu6_configure_spc, INTEL_IPU6);
++
++static void ipu6_internal_pdata_init(struct ipu6_device *isp)
++{
++	u8 hw_ver = isp->hw_ver;
++
++	isys_ipdata.num_parallel_streams = IPU6_ISYS_NUM_STREAMS;
++	isys_ipdata.sram_gran_shift = IPU6_SRAM_GRANULARITY_SHIFT;
++	isys_ipdata.sram_gran_size = IPU6_SRAM_GRANULARITY_SIZE;
++	isys_ipdata.max_sram_size = IPU6_MAX_SRAM_SIZE;
++	isys_ipdata.sensor_type_start = IPU6_FW_ISYS_SENSOR_TYPE_START;
++	isys_ipdata.sensor_type_end = IPU6_FW_ISYS_SENSOR_TYPE_END;
++	isys_ipdata.max_streams = IPU6_ISYS_NUM_STREAMS;
++	isys_ipdata.max_send_queues = IPU6_N_MAX_SEND_QUEUES;
++	isys_ipdata.max_sram_blocks = IPU6_NOF_SRAM_BLOCKS_MAX;
++	isys_ipdata.max_devq_size = IPU6_DEV_SEND_QUEUE_SIZE;
++	isys_ipdata.csi2.nports = ARRAY_SIZE(ipu6_csi_offsets);
++	isys_ipdata.csi2.offsets = ipu6_csi_offsets;
++	isys_ipdata.csi2.irq_mask = IPU6_CSI_RX_ERROR_IRQ_MASK;
++	isys_ipdata.csi2.ctrl0_irq_edge = IPU6_REG_ISYS_CSI_TOP_CTRL0_IRQ_EDGE;
++	isys_ipdata.csi2.ctrl0_irq_clear =
++		IPU6_REG_ISYS_CSI_TOP_CTRL0_IRQ_CLEAR;
++	isys_ipdata.csi2.ctrl0_irq_mask = IPU6_REG_ISYS_CSI_TOP_CTRL0_IRQ_MASK;
++	isys_ipdata.csi2.ctrl0_irq_enable =
++		IPU6_REG_ISYS_CSI_TOP_CTRL0_IRQ_ENABLE;
++	isys_ipdata.csi2.ctrl0_irq_status =
++		IPU6_REG_ISYS_CSI_TOP_CTRL0_IRQ_STATUS;
++	isys_ipdata.csi2.ctrl0_irq_lnp =
++		IPU6_REG_ISYS_CSI_TOP_CTRL0_IRQ_LEVEL_NOT_PULSE;
++	isys_ipdata.enhanced_iwake = is_ipu6ep_mtl(hw_ver) || is_ipu6ep(hw_ver);
++	psys_ipdata.hw_variant.spc_offset = IPU6_PSYS_SPC_OFFSET;
++	isys_ipdata.csi2.fw_access_port_ofs = CSI_REG_HUB_FW_ACCESS_PORT_OFS;
++
++	if (is_ipu6ep(hw_ver)) {
++		isys_ipdata.ltr = IPU6EP_LTR_VALUE;
++		isys_ipdata.memopen_threshold = IPU6EP_MIN_MEMOPEN_TH;
++	}
++
++	if (is_ipu6_tgl(hw_ver)) {
++		isys_ipdata.csi2.nports = ARRAY_SIZE(ipu6_tgl_csi_offsets);
++		isys_ipdata.csi2.offsets = ipu6_tgl_csi_offsets;
++	}
++
++	if (is_ipu6ep_mtl(hw_ver)) {
++		isys_ipdata.csi2.nports = ARRAY_SIZE(ipu6ep_mtl_csi_offsets);
++		isys_ipdata.csi2.offsets = ipu6ep_mtl_csi_offsets;
++
++		isys_ipdata.csi2.ctrl0_irq_edge =
++			IPU6V6_REG_ISYS_CSI_TOP_CTRL0_IRQ_EDGE;
++		isys_ipdata.csi2.ctrl0_irq_clear =
++			IPU6V6_REG_ISYS_CSI_TOP_CTRL0_IRQ_CLEAR;
++		isys_ipdata.csi2.ctrl0_irq_mask =
++			IPU6V6_REG_ISYS_CSI_TOP_CTRL0_IRQ_MASK;
++		isys_ipdata.csi2.ctrl0_irq_enable =
++			IPU6V6_REG_ISYS_CSI_TOP_CTRL0_IRQ_ENABLE;
++		isys_ipdata.csi2.ctrl0_irq_lnp =
++			IPU6V6_REG_ISYS_CSI_TOP_CTRL0_IRQ_LEVEL_NOT_PULSE;
++		isys_ipdata.csi2.ctrl0_irq_status =
++			IPU6V6_REG_ISYS_CSI_TOP_CTRL0_IRQ_STATUS;
++		isys_ipdata.csi2.fw_access_port_ofs =
++			CSI_REG_HUB_FW_ACCESS_PORT_V6OFS;
++		isys_ipdata.ltr = IPU6EP_MTL_LTR_VALUE;
++		isys_ipdata.memopen_threshold = IPU6EP_MTL_MIN_MEMOPEN_TH;
++	}
++
++	if (is_ipu6se(hw_ver)) {
++		isys_ipdata.csi2.nports = ARRAY_SIZE(ipu6se_csi_offsets);
++		isys_ipdata.csi2.irq_mask = IPU6SE_CSI_RX_ERROR_IRQ_MASK;
++		isys_ipdata.csi2.offsets = ipu6se_csi_offsets;
++		isys_ipdata.num_parallel_streams = IPU6SE_ISYS_NUM_STREAMS;
++		isys_ipdata.sram_gran_shift = IPU6SE_SRAM_GRANULARITY_SHIFT;
++		isys_ipdata.sram_gran_size = IPU6SE_SRAM_GRANULARITY_SIZE;
++		isys_ipdata.max_sram_size = IPU6SE_MAX_SRAM_SIZE;
++		isys_ipdata.sensor_type_start =
++			IPU6SE_FW_ISYS_SENSOR_TYPE_START;
++		isys_ipdata.sensor_type_end = IPU6SE_FW_ISYS_SENSOR_TYPE_END;
++		isys_ipdata.max_streams = IPU6SE_ISYS_NUM_STREAMS;
++		isys_ipdata.max_send_queues = IPU6SE_N_MAX_SEND_QUEUES;
++		isys_ipdata.max_sram_blocks = IPU6SE_NOF_SRAM_BLOCKS_MAX;
++		isys_ipdata.max_devq_size = IPU6SE_DEV_SEND_QUEUE_SIZE;
++		psys_ipdata.hw_variant.spc_offset = IPU6SE_PSYS_SPC_OFFSET;
++	}
++}
++
++static int ipu6_isys_check_fwnode_graph(struct fwnode_handle *fwnode)
++{
++	struct fwnode_handle *endpoint;
++
++	if (IS_ERR_OR_NULL(fwnode))
++		return -EINVAL;
++
++	endpoint = fwnode_graph_get_next_endpoint(fwnode, NULL);
++	if (endpoint) {
++		fwnode_handle_put(endpoint);
++		return 0;
++	}
++
++	return ipu6_isys_check_fwnode_graph(fwnode->secondary);
++}
++
++static struct ipu6_bus_device *
++ipu6_isys_init(struct pci_dev *pdev, struct device *parent,
++	       struct ipu6_buttress_ctrl *ctrl, void __iomem *base,
++	       const struct ipu6_isys_internal_pdata *ipdata)
++{
++	struct device *dev = &pdev->dev;
++	struct fwnode_handle *fwnode = dev_fwnode(dev);
++	struct ipu6_bus_device *isys_adev;
++	struct ipu6_isys_pdata *pdata;
++	int ret;
++
++	/* check fwnode at first, fallback into bridge if no fwnode graph */
++	ret = ipu6_isys_check_fwnode_graph(fwnode);
++	if (ret) {
++		if (fwnode && !IS_ERR_OR_NULL(fwnode->secondary)) {
++			dev_err(dev,
++				"fwnode graph has no endpoints connection\n");
++			return ERR_PTR(-EINVAL);
++		}
++
++		ret = ipu_bridge_init(dev, ipu_bridge_parse_ssdb);
++		if (ret) {
++			dev_err_probe(dev, ret, "IPU6 bridge init failed\n");
++			return ERR_PTR(ret);
++		}
++	}
++
++	pdata = kzalloc(sizeof(*pdata), GFP_KERNEL);
++	if (!pdata)
++		return ERR_PTR(-ENOMEM);
++
++	pdata->base = base;
++	pdata->ipdata = ipdata;
++
++	isys_adev = ipu6_bus_initialize_device(pdev, parent, pdata, ctrl,
++					       IPU6_ISYS_NAME);
++	if (IS_ERR(isys_adev)) {
++		dev_err_probe(dev, PTR_ERR(isys_adev),
++			      "ipu6_bus_initialize_device isys failed\n");
++		kfree(pdata);
++		return ERR_CAST(isys_adev);
++	}
++
++	isys_adev->mmu = ipu6_mmu_init(dev, base, ISYS_MMID,
++				       &ipdata->hw_variant);
++	if (IS_ERR(isys_adev->mmu)) {
++		dev_err_probe(dev, PTR_ERR(isys_adev),
++			      "ipu6_mmu_init(isys_adev->mmu) failed\n");
++		put_device(&isys_adev->auxdev.dev);
++		kfree(pdata);
++		return ERR_CAST(isys_adev->mmu);
++	}
++
++	isys_adev->mmu->dev = &isys_adev->auxdev.dev;
++
++	ret = ipu6_bus_add_device(isys_adev);
++	if (ret) {
++		kfree(pdata);
++		return ERR_PTR(ret);
++	}
++
++	return isys_adev;
++}
++
++static struct ipu6_bus_device *
++ipu6_psys_init(struct pci_dev *pdev, struct device *parent,
++	       struct ipu6_buttress_ctrl *ctrl, void __iomem *base,
++	       const struct ipu6_psys_internal_pdata *ipdata)
++{
++	struct ipu6_bus_device *psys_adev;
++	struct ipu6_psys_pdata *pdata;
++	int ret;
++
++	pdata = kzalloc(sizeof(*pdata), GFP_KERNEL);
++	if (!pdata)
++		return ERR_PTR(-ENOMEM);
++
++	pdata->base = base;
++	pdata->ipdata = ipdata;
++
++	psys_adev = ipu6_bus_initialize_device(pdev, parent, pdata, ctrl,
++					       IPU6_PSYS_NAME);
++	if (IS_ERR(psys_adev)) {
++		dev_err_probe(&pdev->dev, PTR_ERR(psys_adev),
++			      "ipu6_bus_initialize_device psys failed\n");
++		kfree(pdata);
++		return ERR_CAST(psys_adev);
++	}
++
++	psys_adev->mmu = ipu6_mmu_init(&pdev->dev, base, PSYS_MMID,
++				       &ipdata->hw_variant);
++	if (IS_ERR(psys_adev->mmu)) {
++		dev_err_probe(&pdev->dev, PTR_ERR(psys_adev),
++			      "ipu6_mmu_init(psys_adev->mmu) failed\n");
++		put_device(&psys_adev->auxdev.dev);
++		kfree(pdata);
++		return ERR_CAST(psys_adev->mmu);
++	}
++
++	psys_adev->mmu->dev = &psys_adev->auxdev.dev;
++
++	ret = ipu6_bus_add_device(psys_adev);
++	if (ret) {
++		kfree(pdata);
++		return ERR_PTR(ret);
++	}
++
++	return psys_adev;
++}
++
++static int ipu6_pci_config_setup(struct pci_dev *dev, u8 hw_ver)
++{
++	int ret;
++
++	/* disable IPU6 PCI ATS on mtl ES2 */
++	if (is_ipu6ep_mtl(hw_ver) && boot_cpu_data.x86_stepping == 0x2 &&
++	    pci_ats_supported(dev))
++		pci_disable_ats(dev);
++
++	/* No PCI msi capability for IPU6EP */
++	if (is_ipu6ep(hw_ver) || is_ipu6ep_mtl(hw_ver)) {
++		/* likely do nothing as msi not enabled by default */
++		pci_disable_msi(dev);
++		return 0;
++	}
++
++	ret = pci_alloc_irq_vectors(dev, 1, 1, PCI_IRQ_MSI);
++	if (ret < 0)
++		return dev_err_probe(&dev->dev, ret, "Request msi failed");
++
++	return 0;
++}
++
++static void ipu6_configure_vc_mechanism(struct ipu6_device *isp)
++{
++	u32 val = readl(isp->base + BUTTRESS_REG_BTRS_CTRL);
++
++	if (IPU6_BTRS_ARB_STALL_MODE_VC0 == IPU6_BTRS_ARB_MODE_TYPE_STALL)
++		val |= BUTTRESS_REG_BTRS_CTRL_STALL_MODE_VC0;
++	else
++		val &= ~BUTTRESS_REG_BTRS_CTRL_STALL_MODE_VC0;
++
++	if (IPU6_BTRS_ARB_STALL_MODE_VC1 == IPU6_BTRS_ARB_MODE_TYPE_STALL)
++		val |= BUTTRESS_REG_BTRS_CTRL_STALL_MODE_VC1;
++	else
++		val &= ~BUTTRESS_REG_BTRS_CTRL_STALL_MODE_VC1;
++
++	writel(val, isp->base + BUTTRESS_REG_BTRS_CTRL);
++}
++
++static int request_cpd_fw(const struct firmware **firmware_p, const char *name,
++			  struct device *device)
++{
++	const struct firmware *fw;
++	struct firmware *dst;
++	int ret = 0;
++
++	ret = request_firmware(&fw, name, device);
++	if (ret)
++		return ret;
++
++	if (is_vmalloc_addr(fw->data)) {
++		*firmware_p = fw;
++		return 0;
++	}
++
++	dst = kzalloc(sizeof(*dst), GFP_KERNEL);
++	if (!dst) {
++		ret = -ENOMEM;
++		goto release_firmware;
++	}
++
++	dst->size = fw->size;
++	dst->data = vmalloc(fw->size);
++	if (!dst->data) {
++		kfree(dst);
++		ret = -ENOMEM;
++		goto release_firmware;
++	}
++
++	memcpy((void *)dst->data, fw->data, fw->size);
++	*firmware_p = dst;
++
++release_firmware:
++	release_firmware(fw);
++
++	return ret;
++}
++
++static int ipu6_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id)
++{
++	struct ipu6_buttress_ctrl *isys_ctrl = NULL, *psys_ctrl = NULL;
++	struct device *dev = &pdev->dev;
++	void __iomem *isys_base = NULL;
++	void __iomem *psys_base = NULL;
++	struct ipu6_device *isp;
++	phys_addr_t phys;
++	u32 val, version, sku_id;
++	int ret;
++
++	isp = devm_kzalloc(dev, sizeof(*isp), GFP_KERNEL);
++	if (!isp)
++		return -ENOMEM;
++
++	isp->pdev = pdev;
++	INIT_LIST_HEAD(&isp->devices);
++
++	ret = pcim_enable_device(pdev);
++	if (ret)
++		return dev_err_probe(dev, ret, "Enable PCI device failed\n");
++
++	phys = pci_resource_start(pdev, IPU6_PCI_BAR);
++	dev_dbg(dev, "IPU6 PCI bar[%u] = %pa\n", IPU6_PCI_BAR, &phys);
++
++	ret = pcim_iomap_regions(pdev, 1 << IPU6_PCI_BAR, pci_name(pdev));
++	if (ret)
++		return dev_err_probe(dev, ret, "Failed to I/O mem remappinp\n");
++
++	isp->base = pcim_iomap_table(pdev)[IPU6_PCI_BAR];
++	pci_set_drvdata(pdev, isp);
++	pci_set_master(pdev);
++
++	isp->cpd_metadata_cmpnt_size = sizeof(struct ipu6_cpd_metadata_cmpnt);
++	switch (id->device) {
++	case PCI_DEVICE_ID_INTEL_IPU6:
++		isp->hw_ver = IPU6_VER_6;
++		isp->cpd_fw_name = IPU6_FIRMWARE_NAME;
++		break;
++	case PCI_DEVICE_ID_INTEL_IPU6SE:
++		isp->hw_ver = IPU6_VER_6SE;
++		isp->cpd_fw_name = IPU6SE_FIRMWARE_NAME;
++		isp->cpd_metadata_cmpnt_size =
++			sizeof(struct ipu6se_cpd_metadata_cmpnt);
++		break;
++	case PCI_DEVICE_ID_INTEL_IPU6EP_ADLP:
++	case PCI_DEVICE_ID_INTEL_IPU6EP_ADLN:
++	case PCI_DEVICE_ID_INTEL_IPU6EP_RPLP:
++		isp->hw_ver = IPU6_VER_6EP;
++		isp->cpd_fw_name = IPU6EP_FIRMWARE_NAME;
++		break;
++	case PCI_DEVICE_ID_INTEL_IPU6EP_MTL:
++		isp->hw_ver = IPU6_VER_6EP_MTL;
++		isp->cpd_fw_name = IPU6EPMTL_FIRMWARE_NAME;
++		break;
++	default:
++		return dev_err_probe(dev, -ENODEV,
++				     "Unsupported IPU6 device %x\n",
++				     id->device);
++	}
++
++	ipu6_internal_pdata_init(isp);
++
++	isys_base = isp->base + isys_ipdata.hw_variant.offset;
++	psys_base = isp->base + psys_ipdata.hw_variant.offset;
++
++	ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(39));
++	if (ret)
++		return dev_err_probe(dev, ret, "Failed to set DMA mask\n");
++
++	ret = dma_set_max_seg_size(dev, UINT_MAX);
++	if (ret)
++		return dev_err_probe(dev, ret, "Failed to set max_seg_size\n");
++
++	ret = ipu6_pci_config_setup(pdev, isp->hw_ver);
++	if (ret)
++		return ret;
++
++	ret = ipu6_buttress_init(isp);
++	if (ret)
++		return ret;
++
++	ret = request_cpd_fw(&isp->cpd_fw, isp->cpd_fw_name, dev);
++	if (ret) {
++		dev_err_probe(&isp->pdev->dev, ret,
++			      "Requesting signed firmware %s failed\n",
++			      isp->cpd_fw_name);
++		goto buttress_exit;
++	}
++
++	ret = ipu6_cpd_validate_cpd_file(isp, isp->cpd_fw->data,
++					 isp->cpd_fw->size);
++	if (ret) {
++		dev_err_probe(&isp->pdev->dev, ret,
++			      "Failed to validate cpd\n");
++		goto out_ipu6_bus_del_devices;
++	}
++
++	isys_ctrl = devm_kmemdup(dev, &isys_buttress_ctrl,
++				 sizeof(isys_buttress_ctrl), GFP_KERNEL);
++	if (!isys_ctrl) {
++		ret = -ENOMEM;
++		goto out_ipu6_bus_del_devices;
++	}
++
++	isp->isys = ipu6_isys_init(pdev, dev, isys_ctrl, isys_base,
++				   &isys_ipdata);
++	if (IS_ERR(isp->isys)) {
++		ret = PTR_ERR(isp->isys);
++		goto out_ipu6_bus_del_devices;
++	}
++
++	psys_ctrl = devm_kmemdup(dev, &psys_buttress_ctrl,
++				 sizeof(psys_buttress_ctrl), GFP_KERNEL);
++	if (!psys_ctrl) {
++		ret = -ENOMEM;
++		goto out_ipu6_bus_del_devices;
++	}
++
++	isp->psys = ipu6_psys_init(pdev, &isp->isys->auxdev.dev, psys_ctrl,
++				   psys_base, &psys_ipdata);
++	if (IS_ERR(isp->psys)) {
++		ret = PTR_ERR(isp->psys);
++		goto out_ipu6_bus_del_devices;
++	}
++
++	ret = pm_runtime_resume_and_get(&isp->psys->auxdev.dev);
++	if (ret < 0)
++		goto out_ipu6_bus_del_devices;
++
++	ret = ipu6_mmu_hw_init(isp->psys->mmu);
++	if (ret) {
++		dev_err_probe(&isp->pdev->dev, ret,
++			      "Failed to set MMU hardware\n");
++		goto out_ipu6_bus_del_devices;
++	}
++
++	ret = ipu6_buttress_map_fw_image(isp->psys, isp->cpd_fw,
++					 &isp->psys->fw_sgt);
++	if (ret) {
++		dev_err_probe(&isp->pdev->dev, ret, "failed to map fw image\n");
++		goto out_ipu6_bus_del_devices;
++	}
++
++	ret = ipu6_cpd_create_pkg_dir(isp->psys, isp->cpd_fw->data);
++	if (ret) {
++		dev_err_probe(&isp->pdev->dev, ret,
++			      "failed to create pkg dir\n");
++		goto out_ipu6_bus_del_devices;
++	}
++
++	ret = devm_request_threaded_irq(dev, pdev->irq, ipu6_buttress_isr,
++					ipu6_buttress_isr_threaded,
++					IRQF_SHARED, IPU6_NAME, isp);
++	if (ret) {
++		dev_err_probe(dev, ret, "Requesting irq failed\n");
++		goto out_ipu6_bus_del_devices;
++	}
++
++	ret = ipu6_buttress_authenticate(isp);
++	if (ret) {
++		dev_err_probe(&isp->pdev->dev, ret,
++			      "FW authentication failed\n");
++		goto out_free_irq;
++	}
++
++	ipu6_mmu_hw_cleanup(isp->psys->mmu);
++	pm_runtime_put(&isp->psys->auxdev.dev);
++
++	/* Configure the arbitration mechanisms for VC requests */
++	ipu6_configure_vc_mechanism(isp);
++
++	val = readl(isp->base + BUTTRESS_REG_SKU);
++	sku_id = FIELD_GET(GENMASK(6, 4), val);
++	version = FIELD_GET(GENMASK(3, 0), val);
++	dev_info(dev, "IPU%u-v%u[%x] hardware version %d\n", version, sku_id,
++		 pdev->device, isp->hw_ver);
++
++	pm_runtime_put_noidle(dev);
++	pm_runtime_allow(dev);
++
++	isp->bus_ready_to_probe = true;
++
++	return 0;
++
++out_free_irq:
++	devm_free_irq(dev, pdev->irq, isp);
++out_ipu6_bus_del_devices:
++	if (isp->psys) {
++		ipu6_cpd_free_pkg_dir(isp->psys);
++		ipu6_buttress_unmap_fw_image(isp->psys, &isp->psys->fw_sgt);
++	}
++	if (!IS_ERR_OR_NULL(isp->psys) && !IS_ERR_OR_NULL(isp->psys->mmu))
++		ipu6_mmu_cleanup(isp->psys->mmu);
++	if (!IS_ERR_OR_NULL(isp->isys) && !IS_ERR_OR_NULL(isp->isys->mmu))
++		ipu6_mmu_cleanup(isp->isys->mmu);
++	ipu6_bus_del_devices(pdev);
++	release_firmware(isp->cpd_fw);
++buttress_exit:
++	ipu6_buttress_exit(isp);
++
++	return ret;
++}
++
++static void ipu6_pci_remove(struct pci_dev *pdev)
++{
++	struct ipu6_device *isp = pci_get_drvdata(pdev);
++	struct ipu6_mmu *isys_mmu = isp->isys->mmu;
++	struct ipu6_mmu *psys_mmu = isp->psys->mmu;
++
++	devm_free_irq(&pdev->dev, pdev->irq, isp);
++	ipu6_cpd_free_pkg_dir(isp->psys);
++
++	ipu6_buttress_unmap_fw_image(isp->psys, &isp->psys->fw_sgt);
++	ipu6_buttress_exit(isp);
++
++	ipu6_bus_del_devices(pdev);
++
++	pm_runtime_forbid(&pdev->dev);
++	pm_runtime_get_noresume(&pdev->dev);
++
++	pci_release_regions(pdev);
++	pci_disable_device(pdev);
++
++	release_firmware(isp->cpd_fw);
++
++	ipu6_mmu_cleanup(psys_mmu);
++	ipu6_mmu_cleanup(isys_mmu);
++}
++
++static void ipu6_pci_reset_prepare(struct pci_dev *pdev)
++{
++	struct ipu6_device *isp = pci_get_drvdata(pdev);
++
++	pm_runtime_forbid(&isp->pdev->dev);
++}
++
++static void ipu6_pci_reset_done(struct pci_dev *pdev)
++{
++	struct ipu6_device *isp = pci_get_drvdata(pdev);
++
++	ipu6_buttress_restore(isp);
++	if (isp->secure_mode)
++		ipu6_buttress_reset_authentication(isp);
++
++	isp->need_ipc_reset = true;
++	pm_runtime_allow(&isp->pdev->dev);
++}
++
++/*
++ * PCI base driver code requires driver to provide these to enable
++ * PCI device level PM state transitions (D0<->D3)
++ */
++static int ipu6_suspend(struct device *dev)
++{
++	return 0;
++}
++
++static int ipu6_resume(struct device *dev)
++{
++	struct pci_dev *pdev = to_pci_dev(dev);
++	struct ipu6_device *isp = pci_get_drvdata(pdev);
++	struct ipu6_buttress *b = &isp->buttress;
++	int ret;
++
++	/* Configure the arbitration mechanisms for VC requests */
++	ipu6_configure_vc_mechanism(isp);
++
++	isp->secure_mode = ipu6_buttress_get_secure_mode(isp);
++	dev_info(dev, "IPU6 in %s mode\n",
++		 isp->secure_mode ? "secure" : "non-secure");
++
++	ipu6_buttress_restore(isp);
++
++	ret = ipu6_buttress_ipc_reset(isp, &b->cse);
++	if (ret)
++		dev_err(&isp->pdev->dev, "IPC reset protocol failed!\n");
++
++	ret = pm_runtime_resume_and_get(&isp->psys->auxdev.dev);
++	if (ret < 0) {
++		dev_err(&isp->psys->auxdev.dev, "Failed to get runtime PM\n");
++		return 0;
++	}
++
++	ret = ipu6_buttress_authenticate(isp);
++	if (ret)
++		dev_err(&isp->pdev->dev, "FW authentication failed(%d)\n", ret);
++
++	pm_runtime_put(&isp->psys->auxdev.dev);
++
++	return 0;
++}
++
++static int ipu6_runtime_resume(struct device *dev)
++{
++	struct pci_dev *pdev = to_pci_dev(dev);
++	struct ipu6_device *isp = pci_get_drvdata(pdev);
++	int ret;
++
++	ipu6_configure_vc_mechanism(isp);
++	ipu6_buttress_restore(isp);
++
++	if (isp->need_ipc_reset) {
++		struct ipu6_buttress *b = &isp->buttress;
++
++		isp->need_ipc_reset = false;
++		ret = ipu6_buttress_ipc_reset(isp, &b->cse);
++		if (ret)
++			dev_err(&isp->pdev->dev, "IPC reset protocol failed\n");
++	}
++
++	return 0;
++}
++
++static const struct dev_pm_ops ipu6_pm_ops = {
++	SET_SYSTEM_SLEEP_PM_OPS(&ipu6_suspend, &ipu6_resume)
++	SET_RUNTIME_PM_OPS(&ipu6_suspend, &ipu6_runtime_resume, NULL)
++};
++
++static const struct pci_device_id ipu6_pci_tbl[] = {
++	{ PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_IPU6) },
++	{ PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_IPU6SE) },
++	{ PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_IPU6EP_ADLP) },
++	{ PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_IPU6EP_ADLN) },
++	{ PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_IPU6EP_RPLP) },
++	{ PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_IPU6EP_MTL) },
++	{ }
++};
++MODULE_DEVICE_TABLE(pci, ipu6_pci_tbl);
++
++static const struct pci_error_handlers pci_err_handlers = {
++	.reset_prepare = ipu6_pci_reset_prepare,
++	.reset_done = ipu6_pci_reset_done,
++};
++
++static struct pci_driver ipu6_pci_driver = {
++	.name = IPU6_NAME,
++	.id_table = ipu6_pci_tbl,
++	.probe = ipu6_pci_probe,
++	.remove = ipu6_pci_remove,
++	.driver = {
++		.pm = pm_ptr(&ipu6_pm_ops),
++	},
++	.err_handler = &pci_err_handlers,
++};
++
++module_pci_driver(ipu6_pci_driver);
++
++MODULE_IMPORT_NS(INTEL_IPU_BRIDGE);
++MODULE_AUTHOR("Sakari Ailus <sakari.ailus@linux.intel.com>");
++MODULE_AUTHOR("Tianshu Qiu <tian.shu.qiu@intel.com>");
++MODULE_AUTHOR("Bingbu Cao <bingbu.cao@intel.com>");
++MODULE_AUTHOR("Qingwu Zhang <qingwu.zhang@intel.com>");
++MODULE_AUTHOR("Yunliang Ding <yunliang.ding@intel.com>");
++MODULE_AUTHOR("Hongju Wang <hongju.wang@intel.com>");
++MODULE_LICENSE("GPL");
++MODULE_DESCRIPTION("Intel IPU6 PCI driver");
+diff --git a/drivers/media/pci/intel/ipu6/ipu6.h b/drivers/media/pci/intel/ipu6/ipu6.h
+new file mode 100644
+index 000000000000..04e7e7e61ca5
+--- /dev/null
++++ b/drivers/media/pci/intel/ipu6/ipu6.h
+@@ -0,0 +1,356 @@
++/* SPDX-License-Identifier: GPL-2.0-only */
++/* Copyright (C) 2013 - 2023 Intel Corporation */
++
++#ifndef IPU6_H
++#define IPU6_H
++
++#include <linux/list.h>
++#include <linux/pci.h>
++#include <linux/types.h>
++
++#include "ipu6-buttress.h"
++
++struct firmware;
++struct pci_dev;
++struct ipu6_bus_device;
++
++#define PCI_DEVICE_ID_INTEL_IPU6		0x9a19
++#define PCI_DEVICE_ID_INTEL_IPU6SE		0x4e19
++#define PCI_DEVICE_ID_INTEL_IPU6EP_ADLP		0x465d
++#define PCI_DEVICE_ID_INTEL_IPU6EP_ADLN		0x462e
++#define PCI_DEVICE_ID_INTEL_IPU6EP_RPLP		0xa75d
++#define PCI_DEVICE_ID_INTEL_IPU6EP_MTL		0x7d19
++
++#define IPU6_NAME			"intel-ipu6"
++#define IPU6_MEDIA_DEV_MODEL_NAME	"ipu6"
++
++#define IPU6SE_FIRMWARE_NAME		"intel/ipu6se_fw.bin"
++#define IPU6EP_FIRMWARE_NAME		"intel/ipu6ep_fw.bin"
++#define IPU6_FIRMWARE_NAME		"intel/ipu6_fw.bin"
++#define IPU6EPMTL_FIRMWARE_NAME		"intel/ipu6epmtl_fw.bin"
++
++enum ipu6_version {
++	IPU6_VER_INVALID = 0,
++	IPU6_VER_6 = 1,
++	IPU6_VER_6SE = 3,
++	IPU6_VER_6EP = 5,
++	IPU6_VER_6EP_MTL = 6,
++};
++
++/*
++ * IPU6 - TGL
++ * IPU6SE - JSL
++ * IPU6EP - ADL/RPL
++ * IPU6EP_MTL - MTL
++ */
++static inline bool is_ipu6se(u8 hw_ver)
++{
++	return hw_ver == IPU6_VER_6SE;
++}
++
++static inline bool is_ipu6ep(u8 hw_ver)
++{
++	return hw_ver == IPU6_VER_6EP;
++}
++
++static inline bool is_ipu6ep_mtl(u8 hw_ver)
++{
++	return hw_ver == IPU6_VER_6EP_MTL;
++}
++
++static inline bool is_ipu6_tgl(u8 hw_ver)
++{
++	return hw_ver == IPU6_VER_6;
++}
++
++/*
++ * ISYS DMA can overshoot. For higher resolutions over allocation is one line
++ * but it must be at minimum 1024 bytes. Value could be different in
++ * different versions / generations thus provide it via platform data.
++ */
++#define IPU6_ISYS_OVERALLOC_MIN		1024
++
++/* Physical pages in GDA is 128, page size is 2K for IPU6, 1K for others */
++#define IPU6_DEVICE_GDA_NR_PAGES		128
++
++/* Virtualization factor to calculate the available virtual pages */
++#define IPU6_DEVICE_GDA_VIRT_FACTOR	32
++
++#define NR_OF_MMU_RESOURCES			2
++
++struct ipu6_device {
++	struct pci_dev *pdev;
++	struct list_head devices;
++	struct ipu6_bus_device *isys;
++	struct ipu6_bus_device *psys;
++	struct ipu6_buttress buttress;
++
++	const struct firmware *cpd_fw;
++	const char *cpd_fw_name;
++	u32 cpd_metadata_cmpnt_size;
++
++	void __iomem *base;
++	bool need_ipc_reset;
++	bool secure_mode;
++	u8 hw_ver;
++	bool bus_ready_to_probe;
++};
++
++#define IPU6_ISYS_NAME "isys"
++#define IPU6_PSYS_NAME "psys"
++
++#define IPU6_MMU_MAX_DEVICES		4
++#define IPU6_MMU_ADDR_BITS		32
++/* The firmware is accessible within the first 2 GiB only in non-secure mode. */
++#define IPU6_MMU_ADDR_BITS_NON_SECURE	31
++
++#define IPU6_MMU_MAX_TLB_L1_STREAMS	32
++#define IPU6_MMU_MAX_TLB_L2_STREAMS	32
++#define IPU6_MAX_LI_BLOCK_ADDR		128
++#define IPU6_MAX_L2_BLOCK_ADDR		64
++
++#define IPU6_ISYS_MAX_CSI2_LEGACY_PORTS	4
++#define IPU6_ISYS_MAX_CSI2_COMBO_PORTS	2
++
++#define IPU6_MAX_FRAME_COUNTER	0xff
++
++#define IPU6SE_ISYS_NUM_STREAMS          IPU6SE_NONSECURE_STREAM_ID_MAX
++#define IPU6_ISYS_NUM_STREAMS            IPU6_NONSECURE_STREAM_ID_MAX
++
++/*
++ * To maximize the IOSF utlization, IPU6 need to send requests in bursts.
++ * At the DMA interface with the buttress, there are CDC FIFOs with burst
++ * collection capability. CDC FIFO burst collectors have a configurable
++ * threshold and is configured based on the outcome of performance measurements.
++ *
++ * isys has 3 ports with IOSF interface for VC0, VC1 and VC2
++ * psys has 4 ports with IOSF interface for VC0, VC1w, VC1r and VC2
++ *
++ * Threshold values are pre-defined and are arrived at after performance
++ * evaluations on a type of IPU6
++ */
++#define IPU6_MAX_VC_IOSF_PORTS		4
++
++/*
++ * IPU6 must configure correct arbitration mechanism related to the IOSF VC
++ * requests. There are two options per VC0 and VC1 - > 0 means rearbitrate on
++ * stall and 1 means stall until the request is completed.
++ */
++#define IPU6_BTRS_ARB_MODE_TYPE_REARB	0
++#define IPU6_BTRS_ARB_MODE_TYPE_STALL	1
++
++/* Currently chosen arbitration mechanism for VC0 */
++#define IPU6_BTRS_ARB_STALL_MODE_VC0	\
++			IPU6_BTRS_ARB_MODE_TYPE_REARB
++
++/* Currently chosen arbitration mechanism for VC1 */
++#define IPU6_BTRS_ARB_STALL_MODE_VC1	\
++			IPU6_BTRS_ARB_MODE_TYPE_REARB
++
++/*
++ * MMU Invalidation HW bug workaround by ZLW mechanism
++ *
++ * Old IPU6 MMUV2 has a bug in the invalidation mechanism which might result in
++ * wrong translation or replication of the translation. This will cause data
++ * corruption. So we cannot directly use the MMU V2 invalidation registers
++ * to invalidate the MMU. Instead, whenever an invalidate is called, we need to
++ * clear the TLB by evicting all the valid translations by filling it with trash
++ * buffer (which is guaranteed not to be used by any other processes). ZLW is
++ * used to fill the L1 and L2 caches with the trash buffer translations. ZLW
++ * or Zero length write, is pre-fetch mechanism to pre-fetch the pages in
++ * advance to the L1 and L2 caches without triggering any memory operations.
++ *
++ * In MMU V2, L1 -> 16 streams and 64 blocks, maximum 16 blocks per stream
++ * One L1 block has 16 entries, hence points to 16 * 4K pages
++ * L2 -> 16 streams and 32 blocks. 2 blocks per streams
++ * One L2 block maps to 1024 L1 entries, hence points to 4MB address range
++ * 2 blocks per L2 stream means, 1 stream points to 8MB range
++ *
++ * As we need to clear the caches and 8MB being the biggest cache size, we need
++ * to have trash buffer which points to 8MB address range. As these trash
++ * buffers are not used for any memory transactions, we need only the least
++ * amount of physical memory. So we reserve 8MB IOVA address range but only
++ * one page is reserved from physical memory. Each of this 8MB IOVA address
++ * range is then mapped to the same physical memory page.
++ */
++/* One L2 entry maps 1024 L1 entries and one L1 entry per page */
++#define IPU6_MMUV2_L2_RANGE		(1024 * PAGE_SIZE)
++/* Max L2 blocks per stream */
++#define IPU6_MMUV2_MAX_L2_BLOCKS	2
++/* Max L1 blocks per stream */
++#define IPU6_MMUV2_MAX_L1_BLOCKS	16
++#define IPU6_MMUV2_TRASH_RANGE	(IPU6_MMUV2_L2_RANGE * IPU6_MMUV2_MAX_L2_BLOCKS)
++/* Entries per L1 block */
++#define MMUV2_ENTRIES_PER_L1_BLOCK	16
++#define MMUV2_TRASH_L1_BLOCK_OFFSET	(MMUV2_ENTRIES_PER_L1_BLOCK * PAGE_SIZE)
++#define MMUV2_TRASH_L2_BLOCK_OFFSET	IPU6_MMUV2_L2_RANGE
++
++/*
++ * In some of the IPU6 MMUs, there is provision to configure L1 and L2 page
++ * table caches. Both these L1 and L2 caches are divided into multiple sections
++ * called streams. There is maximum 16 streams for both caches. Each of these
++ * sections are subdivided into multiple blocks. When nr_l1streams = 0 and
++ * nr_l2streams = 0, means the MMU is of type MMU_V1 and do not support
++ * L1/L2 page table caches.
++ *
++ * L1 stream per block sizes are configurable and varies per usecase.
++ * L2 has constant block sizes - 2 blocks per stream.
++ *
++ * MMU1 support pre-fetching of the pages to have less cache lookup misses. To
++ * enable the pre-fetching, MMU1 AT (Address Translator) device registers
++ * need to be configured.
++ *
++ * There are four types of memory accesses which requires ZLW configuration.
++ * ZLW(Zero Length Write) is a mechanism to enable VT-d pre-fetching on IOMMU.
++ *
++ * 1. Sequential Access or 1D mode
++ *	Set ZLW_EN -> 1
++ *	set ZLW_PAGE_CROSS_1D -> 1
++ *	Set ZLW_N to "N" pages so that ZLW will be inserte N pages ahead where
++ *		  N is pre-defined and hardcoded in the platform data
++ *	Set ZLW_2D -> 0
++ *
++ * 2. ZLW 2D mode
++ *	Set ZLW_EN -> 1
++ *	set ZLW_PAGE_CROSS_1D -> 1,
++ *	Set ZLW_N -> 0
++ *	Set ZLW_2D -> 1
++ *
++ * 3. ZLW Enable (no 1D or 2D mode)
++ *	Set ZLW_EN -> 1
++ *	set ZLW_PAGE_CROSS_1D -> 0,
++ *	Set ZLW_N -> 0
++ *	Set ZLW_2D -> 0
++ *
++ * 4. ZLW disable
++ *	Set ZLW_EN -> 0
++ *	set ZLW_PAGE_CROSS_1D -> 0,
++ *	Set ZLW_N -> 0
++ *	Set ZLW_2D -> 0
++ *
++ * To configure the ZLW for the above memory access, four registers are
++ * available. Hence to track these four settings, we have the following entries
++ * in the struct ipu6_mmu_hw. Each of these entries are per stream and
++ * available only for the L1 streams.
++ *
++ * a. l1_zlw_en -> To track zlw enabled per stream (ZLW_EN)
++ * b. l1_zlw_1d_mode -> Track 1D mode per stream. ZLW inserted at page boundary
++ * c. l1_ins_zlw_ahead_pages -> to track how advance the ZLW need to be inserted
++ *			Insert ZLW request N pages ahead address.
++ * d. l1_zlw_2d_mode -> To track 2D mode per stream (ZLW_2D)
++ *
++ *
++ * Currently L1/L2 streams, blocks, AT ZLW configurations etc. are pre-defined
++ * as per the usecase specific calculations. Any change to this pre-defined
++ * table has to happen in sync with IPU6 FW.
++ */
++struct ipu6_mmu_hw {
++	union {
++		unsigned long offset;
++		void __iomem *base;
++	};
++	u32 info_bits;
++	u8 nr_l1streams;
++	/*
++	 * L1 has variable blocks per stream - total of 64 blocks and maximum of
++	 * 16 blocks per stream. Configurable by using the block start address
++	 * per stream. Block start address is calculated from the block size
++	 */
++	u8 l1_block_sz[IPU6_MMU_MAX_TLB_L1_STREAMS];
++	/* Is ZLW is enabled in each stream */
++	bool l1_zlw_en[IPU6_MMU_MAX_TLB_L1_STREAMS];
++	bool l1_zlw_1d_mode[IPU6_MMU_MAX_TLB_L1_STREAMS];
++	u8 l1_ins_zlw_ahead_pages[IPU6_MMU_MAX_TLB_L1_STREAMS];
++	bool l1_zlw_2d_mode[IPU6_MMU_MAX_TLB_L1_STREAMS];
++
++	u32 l1_stream_id_reg_offset;
++	u32 l2_stream_id_reg_offset;
++
++	u8 nr_l2streams;
++	/*
++	 * L2 has fixed 2 blocks per stream. Block address is calculated
++	 * from the block size
++	 */
++	u8 l2_block_sz[IPU6_MMU_MAX_TLB_L2_STREAMS];
++	/* flag to track if WA is needed for successive invalidate HW bug */
++	bool insert_read_before_invalidate;
++};
++
++struct ipu6_mmu_pdata {
++	u32 nr_mmus;
++	struct ipu6_mmu_hw mmu_hw[IPU6_MMU_MAX_DEVICES];
++	int mmid;
++};
++
++struct ipu6_isys_csi2_pdata {
++	void __iomem *base;
++};
++
++struct ipu6_isys_internal_csi2_pdata {
++	u32 nports;
++	u32 irq_mask;
++	u32 *offsets;
++	u32 ctrl0_irq_edge;
++	u32 ctrl0_irq_clear;
++	u32 ctrl0_irq_mask;
++	u32 ctrl0_irq_enable;
++	u32 ctrl0_irq_lnp;
++	u32 ctrl0_irq_status;
++	u32 fw_access_port_ofs;
++};
++
++struct ipu6_isys_internal_tpg_pdata {
++	u32 ntpgs;
++	u32 *offsets;
++	u32 *sels;
++};
++
++struct ipu6_hw_variants {
++	unsigned long offset;
++	u32 nr_mmus;
++	struct ipu6_mmu_hw mmu_hw[IPU6_MMU_MAX_DEVICES];
++	u8 cdc_fifos;
++	u8 cdc_fifo_threshold[IPU6_MAX_VC_IOSF_PORTS];
++	u32 dmem_offset;
++	u32 spc_offset;
++};
++
++struct ipu6_isys_internal_pdata {
++	struct ipu6_isys_internal_csi2_pdata csi2;
++	struct ipu6_hw_variants hw_variant;
++	u32 num_parallel_streams;
++	u32 isys_dma_overshoot;
++	u32 sram_gran_shift;
++	u32 sram_gran_size;
++	u32 max_sram_size;
++	u32 max_streams;
++	u32 max_send_queues;
++	u32 max_sram_blocks;
++	u32 max_devq_size;
++	u32 sensor_type_start;
++	u32 sensor_type_end;
++	u32 ltr;
++	u32 memopen_threshold;
++	bool enhanced_iwake;
++};
++
++struct ipu6_isys_pdata {
++	void __iomem *base;
++	const struct ipu6_isys_internal_pdata *ipdata;
++};
++
++struct ipu6_psys_internal_pdata {
++	struct ipu6_hw_variants hw_variant;
++};
++
++struct ipu6_psys_pdata {
++	void __iomem *base;
++	const struct ipu6_psys_internal_pdata *ipdata;
++};
++
++int ipu6_fw_authenticate(void *data, u64 val);
++void ipu6_configure_spc(struct ipu6_device *isp,
++			const struct ipu6_hw_variants *hw_variant,
++			int pkg_dir_idx, void __iomem *base, u64 *pkg_dir,
++			dma_addr_t pkg_dir_dma_addr);
++#endif /* IPU6_H */
+-- 
+2.43.2
+
+
+From f52c1b80222269f99d52b0af5937995e22c9ed6d Mon Sep 17 00:00:00 2001
+From: Bingbu Cao <bingbu.cao@intel.com>
+Date: Thu, 11 Jan 2024 14:55:16 +0800
+Subject: [PATCH 09/33] media: intel/ipu6: add IPU auxiliary devices
+
+Even the IPU input system and processing system are in a single PCI
+device, each system has its own power sequence, the processing system
+power up depends on the input system power up.
+
+Besides, input system and processing system have their own MMU
+hardware for IPU DMA address mapping.
+
+Register the IS/PS devices on auxiliary bus and attach power domain
+to implement the power sequence dependency.
+
+Signed-off-by: Bingbu Cao <bingbu.cao@intel.com>
+---
+ drivers/media/pci/intel/ipu6/ipu6-bus.c | 165 ++++++++++++++++++++++++
+ drivers/media/pci/intel/ipu6/ipu6-bus.h |  58 +++++++++
+ 2 files changed, 223 insertions(+)
+ create mode 100644 drivers/media/pci/intel/ipu6/ipu6-bus.c
+ create mode 100644 drivers/media/pci/intel/ipu6/ipu6-bus.h
+
+diff --git a/drivers/media/pci/intel/ipu6/ipu6-bus.c b/drivers/media/pci/intel/ipu6/ipu6-bus.c
+new file mode 100644
+index 000000000000..e81b9a6518a1
+--- /dev/null
++++ b/drivers/media/pci/intel/ipu6/ipu6-bus.c
+@@ -0,0 +1,165 @@
++// SPDX-License-Identifier: GPL-2.0-only
++/*
++ * Copyright (C) 2013 - 2023 Intel Corporation
++ */
++
++#include <linux/auxiliary_bus.h>
++#include <linux/device.h>
++#include <linux/dma-mapping.h>
++#include <linux/err.h>
++#include <linux/list.h>
++#include <linux/mutex.h>
++#include <linux/pci.h>
++#include <linux/pm_domain.h>
++#include <linux/pm_runtime.h>
++#include <linux/slab.h>
++
++#include "ipu6.h"
++#include "ipu6-bus.h"
++#include "ipu6-buttress.h"
++#include "ipu6-dma.h"
++
++static int bus_pm_runtime_suspend(struct device *dev)
++{
++	struct ipu6_bus_device *adev = to_ipu6_bus_device(dev);
++	int ret;
++
++	ret = pm_generic_runtime_suspend(dev);
++	if (ret)
++		return ret;
++
++	ret = ipu6_buttress_power(dev, adev->ctrl, false);
++	if (!ret)
++		return 0;
++
++	dev_err(dev, "power down failed!\n");
++
++	/* Powering down failed, attempt to resume device now */
++	ret = pm_generic_runtime_resume(dev);
++	if (!ret)
++		return -EBUSY;
++
++	return -EIO;
++}
++
++static int bus_pm_runtime_resume(struct device *dev)
++{
++	struct ipu6_bus_device *adev = to_ipu6_bus_device(dev);
++	int ret;
++
++	ret = ipu6_buttress_power(dev, adev->ctrl, true);
++	if (ret)
++		return ret;
++
++	ret = pm_generic_runtime_resume(dev);
++	if (ret)
++		goto out_err;
++
++	return 0;
++
++out_err:
++	ipu6_buttress_power(dev, adev->ctrl, false);
++
++	return -EBUSY;
++}
++
++static struct dev_pm_domain ipu6_bus_pm_domain = {
++	.ops = {
++		.runtime_suspend = bus_pm_runtime_suspend,
++		.runtime_resume = bus_pm_runtime_resume,
++	},
++};
++
++static DEFINE_MUTEX(ipu6_bus_mutex);
++
++static void ipu6_bus_release(struct device *dev)
++{
++	struct ipu6_bus_device *adev = to_ipu6_bus_device(dev);
++
++	kfree(adev->pdata);
++	kfree(adev);
++}
++
++struct ipu6_bus_device *
++ipu6_bus_initialize_device(struct pci_dev *pdev, struct device *parent,
++			   void *pdata, struct ipu6_buttress_ctrl *ctrl,
++			   char *name)
++{
++	struct auxiliary_device *auxdev;
++	struct ipu6_bus_device *adev;
++	struct ipu6_device *isp = pci_get_drvdata(pdev);
++	int ret;
++
++	adev = kzalloc(sizeof(*adev), GFP_KERNEL);
++	if (!adev)
++		return ERR_PTR(-ENOMEM);
++
++	adev->dma_mask = DMA_BIT_MASK(isp->secure_mode ? IPU6_MMU_ADDR_BITS :
++				      IPU6_MMU_ADDR_BITS_NON_SECURE);
++	adev->isp = isp;
++	adev->ctrl = ctrl;
++	adev->pdata = pdata;
++	auxdev = &adev->auxdev;
++	auxdev->name = name;
++	auxdev->id = (pci_domain_nr(pdev->bus) << 16) |
++		      PCI_DEVID(pdev->bus->number, pdev->devfn);
++
++	auxdev->dev.parent = parent;
++	auxdev->dev.release = ipu6_bus_release;
++	auxdev->dev.dma_ops = &ipu6_dma_ops;
++	auxdev->dev.dma_mask = &adev->dma_mask;
++	auxdev->dev.dma_parms = pdev->dev.dma_parms;
++	auxdev->dev.coherent_dma_mask = adev->dma_mask;
++
++	ret = auxiliary_device_init(auxdev);
++	if (ret < 0) {
++		dev_err(&isp->pdev->dev, "auxiliary device init failed (%d)\n",
++			ret);
++		kfree(adev);
++		return ERR_PTR(ret);
++	}
++
++	dev_pm_domain_set(&auxdev->dev, &ipu6_bus_pm_domain);
++
++	pm_runtime_forbid(&adev->auxdev.dev);
++	pm_runtime_enable(&adev->auxdev.dev);
++
++	return adev;
++}
++
++int ipu6_bus_add_device(struct ipu6_bus_device *adev)
++{
++	struct auxiliary_device *auxdev = &adev->auxdev;
++	int ret;
++
++	ret = auxiliary_device_add(auxdev);
++	if (ret) {
++		auxiliary_device_uninit(auxdev);
++		return ret;
++	}
++
++	mutex_lock(&ipu6_bus_mutex);
++	list_add(&adev->list, &adev->isp->devices);
++	mutex_unlock(&ipu6_bus_mutex);
++
++	pm_runtime_allow(&auxdev->dev);
++
++	return 0;
++}
++
++void ipu6_bus_del_devices(struct pci_dev *pdev)
++{
++	struct ipu6_device *isp = pci_get_drvdata(pdev);
++	struct ipu6_bus_device *adev, *save;
++
++	mutex_lock(&ipu6_bus_mutex);
++
++	list_for_each_entry_safe(adev, save, &isp->devices, list) {
++		pm_runtime_disable(&adev->auxdev.dev);
++		list_del(&adev->list);
++		auxiliary_device_delete(&adev->auxdev);
++		auxiliary_device_uninit(&adev->auxdev);
++	}
++
++	mutex_unlock(&ipu6_bus_mutex);
++}
+diff --git a/drivers/media/pci/intel/ipu6/ipu6-bus.h b/drivers/media/pci/intel/ipu6/ipu6-bus.h
+new file mode 100644
+index 000000000000..d46181354836
+--- /dev/null
++++ b/drivers/media/pci/intel/ipu6/ipu6-bus.h
+@@ -0,0 +1,58 @@
++/* SPDX-License-Identifier: GPL-2.0-only */
++/* Copyright (C) 2013 - 2023 Intel Corporation */
++
++#ifndef IPU6_BUS_H
++#define IPU6_BUS_H
++
++#include <linux/auxiliary_bus.h>
++#include <linux/container_of.h>
++#include <linux/device.h>
++#include <linux/irqreturn.h>
++#include <linux/list.h>
++#include <linux/scatterlist.h>
++#include <linux/types.h>
++
++struct firmware;
++struct pci_dev;
++
++#define IPU6_BUS_NAME	IPU6_NAME "-bus"
++
++struct ipu6_buttress_ctrl;
++
++struct ipu6_bus_device {
++	struct auxiliary_device auxdev;
++	struct auxiliary_driver *auxdrv;
++	const struct ipu6_auxdrv_data *auxdrv_data;
++	struct list_head list;
++	void *pdata;
++	struct ipu6_mmu *mmu;
++	struct ipu6_device *isp;
++	struct ipu6_buttress_ctrl *ctrl;
++	u64 dma_mask;
++	const struct firmware *fw;
++	struct sg_table fw_sgt;
++	u64 *pkg_dir;
++	dma_addr_t pkg_dir_dma_addr;
++	unsigned int pkg_dir_size;
++};
++
++struct ipu6_auxdrv_data {
++	irqreturn_t (*isr)(struct ipu6_bus_device *adev);
++	irqreturn_t (*isr_threaded)(struct ipu6_bus_device *adev);
++	bool wake_isr_thread;
++};
++
++#define to_ipu6_bus_device(_dev) \
++	container_of(to_auxiliary_dev(_dev), struct ipu6_bus_device, auxdev)
++#define auxdev_to_adev(_auxdev) \
++	container_of(_auxdev, struct ipu6_bus_device, auxdev)
++#define ipu6_bus_get_drvdata(adev) dev_get_drvdata(&(adev)->auxdev.dev)
++
++struct ipu6_bus_device *
++ipu6_bus_initialize_device(struct pci_dev *pdev, struct device *parent,
++			   void *pdata, struct ipu6_buttress_ctrl *ctrl,
++			   char *name);
++int ipu6_bus_add_device(struct ipu6_bus_device *adev);
++void ipu6_bus_del_devices(struct pci_dev *pdev);
++
++#endif
+-- 
+2.43.2
+
+
+From a74d85716ec13ff2f55997c73c9f06367174d7a6 Mon Sep 17 00:00:00 2001
+From: Bingbu Cao <bingbu.cao@intel.com>
+Date: Thu, 11 Jan 2024 14:55:17 +0800
+Subject: [PATCH 10/33] media: intel/ipu6: add IPU6 buttress interface driver
+
+The IPU6 buttress is the interface between IPU device (input system
+and processing system) with rest of the SoC. It contains overall IPU
+hardware control registers, these control registers are used as the
+interfaces with the Intel Converged Security Engine and Punit to do
+firmware authentication and power management.
+
+Signed-off-by: Bingbu Cao <bingbu.cao@intel.com>
+---
+ drivers/media/pci/intel/ipu6/ipu6-buttress.c  | 912 ++++++++++++++++++
+ drivers/media/pci/intel/ipu6/ipu6-buttress.h  | 102 ++
+ .../intel/ipu6/ipu6-platform-buttress-regs.h  | 232 +++++
+ 3 files changed, 1246 insertions(+)
+ create mode 100644 drivers/media/pci/intel/ipu6/ipu6-buttress.c
+ create mode 100644 drivers/media/pci/intel/ipu6/ipu6-buttress.h
+ create mode 100644 drivers/media/pci/intel/ipu6/ipu6-platform-buttress-regs.h
+
+diff --git a/drivers/media/pci/intel/ipu6/ipu6-buttress.c b/drivers/media/pci/intel/ipu6/ipu6-buttress.c
+new file mode 100644
+index 000000000000..2f73302812f3
+--- /dev/null
++++ b/drivers/media/pci/intel/ipu6/ipu6-buttress.c
+@@ -0,0 +1,912 @@
++// SPDX-License-Identifier: GPL-2.0-only
++/*
++ * Copyright (C) 2013 - 2023 Intel Corporation
++ */
++
++#include <linux/bitfield.h>
++#include <linux/bits.h>
++#include <linux/completion.h>
++#include <linux/delay.h>
++#include <linux/device.h>
++#include <linux/dma-mapping.h>
++#include <linux/firmware.h>
++#include <linux/interrupt.h>
++#include <linux/iopoll.h>
++#include <linux/math64.h>
++#include <linux/mm.h>
++#include <linux/mutex.h>
++#include <linux/pci.h>
++#include <linux/pfn.h>
++#include <linux/pm_runtime.h>
++#include <linux/scatterlist.h>
++#include <linux/slab.h>
++#include <linux/time64.h>
++
++#include "ipu6.h"
++#include "ipu6-bus.h"
++#include "ipu6-buttress.h"
++#include "ipu6-platform-buttress-regs.h"
++
++#define BOOTLOADER_STATUS_OFFSET       0x15c
++
++#define BOOTLOADER_MAGIC_KEY		0xb00710ad
++
++#define ENTRY	BUTTRESS_IU2CSECSR_IPC_PEER_COMP_ACTIONS_RST_PHASE1
++#define EXIT	BUTTRESS_IU2CSECSR_IPC_PEER_COMP_ACTIONS_RST_PHASE2
++#define QUERY	BUTTRESS_IU2CSECSR_IPC_PEER_QUERIED_IP_COMP_ACTIONS_RST_PHASE
++
++#define BUTTRESS_TSC_SYNC_RESET_TRIAL_MAX	10
++
++#define BUTTRESS_POWER_TIMEOUT_US		(200 * USEC_PER_MSEC)
++
++#define BUTTRESS_CSE_BOOTLOAD_TIMEOUT_US	(5 * USEC_PER_SEC)
++#define BUTTRESS_CSE_AUTHENTICATE_TIMEOUT_US	(10 * USEC_PER_SEC)
++#define BUTTRESS_CSE_FWRESET_TIMEOUT_US		(100 * USEC_PER_MSEC)
++
++#define BUTTRESS_IPC_TX_TIMEOUT_MS		MSEC_PER_SEC
++#define BUTTRESS_IPC_RX_TIMEOUT_MS		MSEC_PER_SEC
++#define BUTTRESS_IPC_VALIDITY_TIMEOUT_US	(1 * USEC_PER_SEC)
++#define BUTTRESS_TSC_SYNC_TIMEOUT_US		(5 * USEC_PER_MSEC)
++
++#define BUTTRESS_IPC_RESET_RETRY		2000
++#define BUTTRESS_CSE_IPC_RESET_RETRY	4
++#define BUTTRESS_IPC_CMD_SEND_RETRY	1
++
++#define BUTTRESS_MAX_CONSECUTIVE_IRQS	100
++
++static const u32 ipu6_adev_irq_mask[2] = {
++	BUTTRESS_ISR_IS_IRQ,
++	BUTTRESS_ISR_PS_IRQ
++};
++
++int ipu6_buttress_ipc_reset(struct ipu6_device *isp,
++			    struct ipu6_buttress_ipc *ipc)
++{
++	unsigned int retries = BUTTRESS_IPC_RESET_RETRY;
++	struct ipu6_buttress *b = &isp->buttress;
++	u32 val = 0, csr_in_clr;
++
++	if (!isp->secure_mode) {
++		dev_dbg(&isp->pdev->dev, "Skip IPC reset for non-secure mode");
++		return 0;
++	}
++
++	mutex_lock(&b->ipc_mutex);
++
++	/* Clear-by-1 CSR (all bits), corresponding internal states. */
++	val = readl(isp->base + ipc->csr_in);
++	writel(val, isp->base + ipc->csr_in);
++
++	/* Set peer CSR bit IPC_PEER_COMP_ACTIONS_RST_PHASE1 */
++	writel(ENTRY, isp->base + ipc->csr_out);
++	/*
++	 * Clear-by-1 all CSR bits EXCEPT following
++	 * bits:
++	 * A. IPC_PEER_COMP_ACTIONS_RST_PHASE1.
++	 * B. IPC_PEER_COMP_ACTIONS_RST_PHASE2.
++	 * C. Possibly custom bits, depending on
++	 * their role.
++	 */
++	csr_in_clr = BUTTRESS_IU2CSECSR_IPC_PEER_DEASSERTED_REG_VALID_REQ |
++		BUTTRESS_IU2CSECSR_IPC_PEER_ACKED_REG_VALID |
++		BUTTRESS_IU2CSECSR_IPC_PEER_ASSERTED_REG_VALID_REQ | QUERY;
++
++	do {
++		usleep_range(400, 500);
++		val = readl(isp->base + ipc->csr_in);
++		switch (val) {
++		case ENTRY | EXIT:
++		case ENTRY | EXIT | QUERY:
++			/*
++			 * 1) Clear-by-1 CSR bits
++			 * (IPC_PEER_COMP_ACTIONS_RST_PHASE1,
++			 * IPC_PEER_COMP_ACTIONS_RST_PHASE2).
++			 * 2) Set peer CSR bit
++			 * IPC_PEER_QUERIED_IP_COMP_ACTIONS_RST_PHASE.
++			 */
++			writel(ENTRY | EXIT, isp->base + ipc->csr_in);
++			writel(QUERY, isp->base + ipc->csr_out);
++			break;
++		case ENTRY:
++		case ENTRY | QUERY:
++			/*
++			 * 1) Clear-by-1 CSR bits
++			 * (IPC_PEER_COMP_ACTIONS_RST_PHASE1,
++			 * IPC_PEER_QUERIED_IP_COMP_ACTIONS_RST_PHASE).
++			 * 2) Set peer CSR bit
++			 * IPC_PEER_COMP_ACTIONS_RST_PHASE1.
++			 */
++			writel(ENTRY | QUERY, isp->base + ipc->csr_in);
++			writel(ENTRY, isp->base + ipc->csr_out);
++			break;
++		case EXIT:
++		case EXIT | QUERY:
++			/*
++			 * Clear-by-1 CSR bit
++			 * IPC_PEER_COMP_ACTIONS_RST_PHASE2.
++			 * 1) Clear incoming doorbell.
++			 * 2) Clear-by-1 all CSR bits EXCEPT following
++			 * bits:
++			 * A. IPC_PEER_COMP_ACTIONS_RST_PHASE1.
++			 * B. IPC_PEER_COMP_ACTIONS_RST_PHASE2.
++			 * C. Possibly custom bits, depending on
++			 * their role.
++			 * 3) Set peer CSR bit
++			 * IPC_PEER_COMP_ACTIONS_RST_PHASE2.
++			 */
++			writel(EXIT, isp->base + ipc->csr_in);
++			writel(0, isp->base + ipc->db0_in);
++			writel(csr_in_clr, isp->base + ipc->csr_in);
++			writel(EXIT, isp->base + ipc->csr_out);
++
++			/*
++			 * Read csr_in again to make sure if RST_PHASE2 is done.
++			 * If csr_in is QUERY, it should be handled again.
++			 */
++			usleep_range(200, 300);
++			val = readl(isp->base + ipc->csr_in);
++			if (val & QUERY) {
++				dev_dbg(&isp->pdev->dev,
++					"RST_PHASE2 retry csr_in = %x\n", val);
++				break;
++			}
++			mutex_unlock(&b->ipc_mutex);
++			return 0;
++		case QUERY:
++			/*
++			 * 1) Clear-by-1 CSR bit
++			 * IPC_PEER_QUERIED_IP_COMP_ACTIONS_RST_PHASE.
++			 * 2) Set peer CSR bit
++			 * IPC_PEER_COMP_ACTIONS_RST_PHASE1
++			 */
++			writel(QUERY, isp->base + ipc->csr_in);
++			writel(ENTRY, isp->base + ipc->csr_out);
++			break;
++		default:
++			dev_warn_ratelimited(&isp->pdev->dev,
++					     "Unexpected CSR 0x%x\n", val);
++			break;
++		}
++	} while (retries--);
++
++	mutex_unlock(&b->ipc_mutex);
++	dev_err(&isp->pdev->dev, "Timed out while waiting for CSE\n");
++
++	return -ETIMEDOUT;
++}
++
++static void ipu6_buttress_ipc_validity_close(struct ipu6_device *isp,
++					     struct ipu6_buttress_ipc *ipc)
++{
++	writel(BUTTRESS_IU2CSECSR_IPC_PEER_DEASSERTED_REG_VALID_REQ,
++	       isp->base + ipc->csr_out);
++}
++
++static int
++ipu6_buttress_ipc_validity_open(struct ipu6_device *isp,
++				struct ipu6_buttress_ipc *ipc)
++{
++	unsigned int mask = BUTTRESS_IU2CSECSR_IPC_PEER_ACKED_REG_VALID;
++	void __iomem *addr;
++	int ret;
++	u32 val;
++
++	writel(BUTTRESS_IU2CSECSR_IPC_PEER_ASSERTED_REG_VALID_REQ,
++	       isp->base + ipc->csr_out);
++
++	addr = isp->base + ipc->csr_in;
++	ret = readl_poll_timeout(addr, val, val & mask, 200,
++				 BUTTRESS_IPC_VALIDITY_TIMEOUT_US);
++	if (ret) {
++		dev_err(&isp->pdev->dev, "CSE validity timeout 0x%x\n", val);
++		ipu6_buttress_ipc_validity_close(isp, ipc);
++	}
++
++	return ret;
++}
++
++static void ipu6_buttress_ipc_recv(struct ipu6_device *isp,
++				   struct ipu6_buttress_ipc *ipc, u32 *ipc_msg)
++{
++	if (ipc_msg)
++		*ipc_msg = readl(isp->base + ipc->data0_in);
++	writel(0, isp->base + ipc->db0_in);
++}
++
++static int ipu6_buttress_ipc_send_bulk(struct ipu6_device *isp,
++				       enum ipu6_buttress_ipc_domain ipc_domain,
++				       struct ipu6_ipc_buttress_bulk_msg *msgs,
++				       u32 size)
++{
++	unsigned long tx_timeout_jiffies, rx_timeout_jiffies;
++	unsigned int i, retry = BUTTRESS_IPC_CMD_SEND_RETRY;
++	struct ipu6_buttress *b = &isp->buttress;
++	struct ipu6_buttress_ipc *ipc;
++	u32 val;
++	int ret;
++	int tout;
++
++	ipc = ipc_domain == IPU6_BUTTRESS_IPC_CSE ? &b->cse : &b->ish;
++
++	mutex_lock(&b->ipc_mutex);
++
++	ret = ipu6_buttress_ipc_validity_open(isp, ipc);
++	if (ret) {
++		dev_err(&isp->pdev->dev, "IPC validity open failed\n");
++		goto out;
++	}
++
++	tx_timeout_jiffies = msecs_to_jiffies(BUTTRESS_IPC_TX_TIMEOUT_MS);
++	rx_timeout_jiffies = msecs_to_jiffies(BUTTRESS_IPC_RX_TIMEOUT_MS);
++
++	for (i = 0; i < size; i++) {
++		reinit_completion(&ipc->send_complete);
++		if (msgs[i].require_resp)
++			reinit_completion(&ipc->recv_complete);
++
++		dev_dbg(&isp->pdev->dev, "bulk IPC command: 0x%x\n",
++			msgs[i].cmd);
++		writel(msgs[i].cmd, isp->base + ipc->data0_out);
++		val = BUTTRESS_IU2CSEDB0_BUSY | msgs[i].cmd_size;
++		writel(val, isp->base + ipc->db0_out);
++
++		tout = wait_for_completion_timeout(&ipc->send_complete,
++						   tx_timeout_jiffies);
++		if (!tout) {
++			dev_err(&isp->pdev->dev, "send IPC response timeout\n");
++			if (!retry--) {
++				ret = -ETIMEDOUT;
++				goto out;
++			}
++
++			/* Try again if CSE is not responding on first try */
++			writel(0, isp->base + ipc->db0_out);
++			i--;
++			continue;
++		}
++
++		retry = BUTTRESS_IPC_CMD_SEND_RETRY;
++
++		if (!msgs[i].require_resp)
++			continue;
++
++		tout = wait_for_completion_timeout(&ipc->recv_complete,
++						   rx_timeout_jiffies);
++		if (!tout) {
++			dev_err(&isp->pdev->dev, "recv IPC response timeout\n");
++			ret = -ETIMEDOUT;
++			goto out;
++		}
++
++		if (ipc->nack_mask &&
++		    (ipc->recv_data & ipc->nack_mask) == ipc->nack) {
++			dev_err(&isp->pdev->dev,
++				"IPC NACK for cmd 0x%x\n", msgs[i].cmd);
++			ret = -EIO;
++			goto out;
++		}
++
++		if (ipc->recv_data != msgs[i].expected_resp) {
++			dev_err(&isp->pdev->dev,
++				"expected resp: 0x%x, IPC response: 0x%x ",
++				msgs[i].expected_resp, ipc->recv_data);
++			ret = -EIO;
++			goto out;
++		}
++	}
++
++	dev_dbg(&isp->pdev->dev, "bulk IPC commands done\n");
++
++out:
++	ipu6_buttress_ipc_validity_close(isp, ipc);
++	mutex_unlock(&b->ipc_mutex);
++	return ret;
++}
++
++static int
++ipu6_buttress_ipc_send(struct ipu6_device *isp,
++		       enum ipu6_buttress_ipc_domain ipc_domain,
++		       u32 ipc_msg, u32 size, bool require_resp,
++		       u32 expected_resp)
++{
++	struct ipu6_ipc_buttress_bulk_msg msg = {
++		.cmd = ipc_msg,
++		.cmd_size = size,
++		.require_resp = require_resp,
++		.expected_resp = expected_resp,
++	};
++
++	return ipu6_buttress_ipc_send_bulk(isp, ipc_domain, &msg, 1);
++}
++
++static irqreturn_t ipu6_buttress_call_isr(struct ipu6_bus_device *adev)
++{
++	irqreturn_t ret = IRQ_WAKE_THREAD;
++
++	if (!adev || !adev->auxdrv || !adev->auxdrv_data)
++		return IRQ_NONE;
++
++	if (adev->auxdrv_data->isr)
++		ret = adev->auxdrv_data->isr(adev);
++
++	if (ret == IRQ_WAKE_THREAD && !adev->auxdrv_data->isr_threaded)
++		ret = IRQ_NONE;
++
++	return ret;
++}
++
++irqreturn_t ipu6_buttress_isr(int irq, void *isp_ptr)
++{
++	struct ipu6_device *isp = isp_ptr;
++	struct ipu6_bus_device *adev[] = { isp->isys, isp->psys };
++	struct ipu6_buttress *b = &isp->buttress;
++	u32 reg_irq_sts = BUTTRESS_REG_ISR_STATUS;
++	irqreturn_t ret = IRQ_NONE;
++	u32 disable_irqs = 0;
++	u32 irq_status;
++	u32 i, count = 0;
++
++	pm_runtime_get_noresume(&isp->pdev->dev);
++
++	irq_status = readl(isp->base + reg_irq_sts);
++	if (!irq_status) {
++		pm_runtime_put_noidle(&isp->pdev->dev);
++		return IRQ_NONE;
++	}
++
++	do {
++		writel(irq_status, isp->base + BUTTRESS_REG_ISR_CLEAR);
++
++		for (i = 0; i < ARRAY_SIZE(ipu6_adev_irq_mask); i++) {
++			irqreturn_t r = ipu6_buttress_call_isr(adev[i]);
++
++			if (!(irq_status & ipu6_adev_irq_mask[i]))
++				continue;
++
++			if (r == IRQ_WAKE_THREAD) {
++				ret = IRQ_WAKE_THREAD;
++				disable_irqs |= ipu6_adev_irq_mask[i];
++			} else if (ret == IRQ_NONE && r == IRQ_HANDLED) {
++				ret = IRQ_HANDLED;
++			}
++		}
++
++		if ((irq_status & BUTTRESS_EVENT) && ret == IRQ_NONE)
++			ret = IRQ_HANDLED;
++
++		if (irq_status & BUTTRESS_ISR_IPC_FROM_CSE_IS_WAITING) {
++			dev_dbg(&isp->pdev->dev,
++				"BUTTRESS_ISR_IPC_FROM_CSE_IS_WAITING\n");
++			ipu6_buttress_ipc_recv(isp, &b->cse, &b->cse.recv_data);
++			complete(&b->cse.recv_complete);
++		}
++
++		if (irq_status & BUTTRESS_ISR_IPC_FROM_ISH_IS_WAITING) {
++			dev_dbg(&isp->pdev->dev,
++				"BUTTRESS_ISR_IPC_FROM_ISH_IS_WAITING\n");
++			ipu6_buttress_ipc_recv(isp, &b->ish, &b->ish.recv_data);
++			complete(&b->ish.recv_complete);
++		}
++
++		if (irq_status & BUTTRESS_ISR_IPC_EXEC_DONE_BY_CSE) {
++			dev_dbg(&isp->pdev->dev,
++				"BUTTRESS_ISR_IPC_EXEC_DONE_BY_CSE\n");
++			complete(&b->cse.send_complete);
++		}
++
++		if (irq_status & BUTTRESS_ISR_IPC_EXEC_DONE_BY_ISH) {
++			dev_dbg(&isp->pdev->dev,
++				"BUTTRESS_ISR_IPC_EXEC_DONE_BY_CSE\n");
++			complete(&b->ish.send_complete);
++		}
++
++		if (irq_status & BUTTRESS_ISR_SAI_VIOLATION &&
++		    ipu6_buttress_get_secure_mode(isp))
++			dev_err(&isp->pdev->dev,
++				"BUTTRESS_ISR_SAI_VIOLATION\n");
++
++		if (irq_status & (BUTTRESS_ISR_IS_FATAL_MEM_ERR |
++				  BUTTRESS_ISR_PS_FATAL_MEM_ERR))
++			dev_err(&isp->pdev->dev,
++				"BUTTRESS_ISR_FATAL_MEM_ERR\n");
++
++		if (irq_status & BUTTRESS_ISR_UFI_ERROR)
++			dev_err(&isp->pdev->dev, "BUTTRESS_ISR_UFI_ERROR\n");
++
++		if (++count == BUTTRESS_MAX_CONSECUTIVE_IRQS) {
++			dev_err(&isp->pdev->dev, "too many consecutive IRQs\n");
++			ret = IRQ_NONE;
++			break;
++		}
++
++		irq_status = readl(isp->base + reg_irq_sts);
++	} while (irq_status);
++
++	if (disable_irqs)
++		writel(BUTTRESS_IRQS & ~disable_irqs,
++		       isp->base + BUTTRESS_REG_ISR_ENABLE);
++
++	pm_runtime_put(&isp->pdev->dev);
++
++	return ret;
++}
++
++irqreturn_t ipu6_buttress_isr_threaded(int irq, void *isp_ptr)
++{
++	struct ipu6_device *isp = isp_ptr;
++	struct ipu6_bus_device *adev[] = { isp->isys, isp->psys };
++	const struct ipu6_auxdrv_data *drv_data = NULL;
++	irqreturn_t ret = IRQ_NONE;
++	unsigned int i;
++
++	for (i = 0; i < ARRAY_SIZE(ipu6_adev_irq_mask) && adev[i]; i++) {
++		drv_data = adev[i]->auxdrv_data;
++		if (!drv_data)
++			continue;
++
++		if (drv_data->wake_isr_thread &&
++		    drv_data->isr_threaded(adev[i]) == IRQ_HANDLED)
++			ret = IRQ_HANDLED;
++	}
++
++	writel(BUTTRESS_IRQS, isp->base + BUTTRESS_REG_ISR_ENABLE);
++
++	return ret;
++}
++
++int ipu6_buttress_power(struct device *dev, struct ipu6_buttress_ctrl *ctrl,
++			bool on)
++{
++	struct ipu6_device *isp = to_ipu6_bus_device(dev)->isp;
++	u32 pwr_sts, val;
++	int ret;
++
++	if (!ctrl)
++		return 0;
++
++	mutex_lock(&isp->buttress.power_mutex);
++
++	if (!on) {
++		val = 0;
++		pwr_sts = ctrl->pwr_sts_off << ctrl->pwr_sts_shift;
++	} else {
++		val = BUTTRESS_FREQ_CTL_START |
++			FIELD_PREP(BUTTRESS_FREQ_CTL_RATIO_MASK,
++				   ctrl->ratio) |
++			FIELD_PREP(BUTTRESS_FREQ_CTL_QOS_FLOOR_MASK,
++				   ctrl->qos_floor) |
++			BUTTRESS_FREQ_CTL_ICCMAX_LEVEL;
++
++		pwr_sts = ctrl->pwr_sts_on << ctrl->pwr_sts_shift;
++	}
++
++	writel(val, isp->base + ctrl->freq_ctl);
++
++	ret = readl_poll_timeout(isp->base + BUTTRESS_REG_PWR_STATE,
++				 val, (val & ctrl->pwr_sts_mask) == pwr_sts,
++				 100, BUTTRESS_POWER_TIMEOUT_US);
++	if (ret)
++		dev_err(&isp->pdev->dev,
++			"Change power status timeout with 0x%x\n", val);
++
++	ctrl->started = !ret && on;
++
++	mutex_unlock(&isp->buttress.power_mutex);
++
++	return ret;
++}
++
++bool ipu6_buttress_get_secure_mode(struct ipu6_device *isp)
++{
++	u32 val;
++
++	val = readl(isp->base + BUTTRESS_REG_SECURITY_CTL);
++
++	return val & BUTTRESS_SECURITY_CTL_FW_SECURE_MODE;
++}
++
++bool ipu6_buttress_auth_done(struct ipu6_device *isp)
++{
++	u32 val;
++
++	if (!isp->secure_mode)
++		return true;
++
++	val = readl(isp->base + BUTTRESS_REG_SECURITY_CTL);
++	val = FIELD_GET(BUTTRESS_SECURITY_CTL_FW_SETUP_MASK, val);
++
++	return val == BUTTRESS_SECURITY_CTL_AUTH_DONE;
++}
++EXPORT_SYMBOL_NS_GPL(ipu6_buttress_auth_done, INTEL_IPU6);
++
++int ipu6_buttress_reset_authentication(struct ipu6_device *isp)
++{
++	int ret;
++	u32 val;
++
++	if (!isp->secure_mode) {
++		dev_dbg(&isp->pdev->dev, "Skip auth for non-secure mode\n");
++		return 0;
++	}
++
++	writel(BUTTRESS_FW_RESET_CTL_START, isp->base +
++	       BUTTRESS_REG_FW_RESET_CTL);
++
++	ret = readl_poll_timeout(isp->base + BUTTRESS_REG_FW_RESET_CTL, val,
++				 val & BUTTRESS_FW_RESET_CTL_DONE, 500,
++				 BUTTRESS_CSE_FWRESET_TIMEOUT_US);
++	if (ret) {
++		dev_err(&isp->pdev->dev,
++			"Time out while resetting authentication state\n");
++		return ret;
++	}
++
++	dev_dbg(&isp->pdev->dev, "FW reset for authentication done\n");
++	writel(0, isp->base + BUTTRESS_REG_FW_RESET_CTL);
++	/* leave some time for HW restore */
++	usleep_range(800, 1000);
++
++	return 0;
++}
++
++int ipu6_buttress_map_fw_image(struct ipu6_bus_device *sys,
++			       const struct firmware *fw, struct sg_table *sgt)
++{
++	struct page **pages;
++	const void *addr;
++	unsigned long n_pages;
++	unsigned int i;
++	int ret;
++
++	n_pages = PHYS_PFN(PAGE_ALIGN(fw->size));
++
++	pages = kmalloc_array(n_pages, sizeof(*pages), GFP_KERNEL);
++	if (!pages)
++		return -ENOMEM;
++
++	addr = fw->data;
++	for (i = 0; i < n_pages; i++) {
++		struct page *p = vmalloc_to_page(addr);
++
++		if (!p) {
++			ret = -ENOMEM;
++			goto out;
++		}
++		pages[i] = p;
++		addr += PAGE_SIZE;
++	}
++
++	ret = sg_alloc_table_from_pages(sgt, pages, n_pages, 0, fw->size,
++					GFP_KERNEL);
++	if (ret) {
++		ret = -ENOMEM;
++		goto out;
++	}
++
++	ret = dma_map_sgtable(&sys->auxdev.dev, sgt, DMA_TO_DEVICE, 0);
++	if (ret < 0) {
++		ret = -ENOMEM;
++		sg_free_table(sgt);
++		goto out;
++	}
++
++	dma_sync_sgtable_for_device(&sys->auxdev.dev, sgt, DMA_TO_DEVICE);
++
++out:
++	kfree(pages);
++
++	return ret;
++}
++EXPORT_SYMBOL_NS_GPL(ipu6_buttress_map_fw_image, INTEL_IPU6);
++
++void ipu6_buttress_unmap_fw_image(struct ipu6_bus_device *sys,
++				  struct sg_table *sgt)
++{
++	dma_unmap_sgtable(&sys->auxdev.dev, sgt, DMA_TO_DEVICE, 0);
++	sg_free_table(sgt);
++}
++EXPORT_SYMBOL_NS_GPL(ipu6_buttress_unmap_fw_image, INTEL_IPU6);
++
++int ipu6_buttress_authenticate(struct ipu6_device *isp)
++{
++	struct ipu6_buttress *b = &isp->buttress;
++	struct ipu6_psys_pdata *psys_pdata;
++	u32 data, mask, done, fail;
++	int ret;
++
++	if (!isp->secure_mode) {
++		dev_dbg(&isp->pdev->dev, "Skip auth for non-secure mode\n");
++		return 0;
++	}
++
++	psys_pdata = isp->psys->pdata;
++
++	mutex_lock(&b->auth_mutex);
++
++	if (ipu6_buttress_auth_done(isp)) {
++		ret = 0;
++		goto out_unlock;
++	}
++
++	/*
++	 * Write address of FIT table to FW_SOURCE register
++	 * Let's use fw address. I.e. not using FIT table yet
++	 */
++	data = lower_32_bits(isp->psys->pkg_dir_dma_addr);
++	writel(data, isp->base + BUTTRESS_REG_FW_SOURCE_BASE_LO);
++
++	data = upper_32_bits(isp->psys->pkg_dir_dma_addr);
++	writel(data, isp->base + BUTTRESS_REG_FW_SOURCE_BASE_HI);
++
++	/*
++	 * Write boot_load into IU2CSEDATA0
++	 * Write sizeof(boot_load) | 0x2 << CLIENT_ID to
++	 * IU2CSEDB.IU2CSECMD and set IU2CSEDB.IU2CSEBUSY as
++	 */
++	dev_info(&isp->pdev->dev, "Sending BOOT_LOAD to CSE\n");
++
++	ret = ipu6_buttress_ipc_send(isp, IPU6_BUTTRESS_IPC_CSE,
++				     BUTTRESS_IU2CSEDATA0_IPC_BOOT_LOAD,
++				     1, true,
++				     BUTTRESS_CSE2IUDATA0_IPC_BOOT_LOAD_DONE);
++	if (ret) {
++		dev_err(&isp->pdev->dev, "CSE boot_load failed\n");
++		goto out_unlock;
++	}
++
++	mask = BUTTRESS_SECURITY_CTL_FW_SETUP_MASK;
++	done = BUTTRESS_SECURITY_CTL_FW_SETUP_DONE;
++	fail = BUTTRESS_SECURITY_CTL_AUTH_FAILED;
++	ret = readl_poll_timeout(isp->base + BUTTRESS_REG_SECURITY_CTL, data,
++				 ((data & mask) == done ||
++				  (data & mask) == fail), 500,
++				 BUTTRESS_CSE_BOOTLOAD_TIMEOUT_US);
++	if (ret) {
++		dev_err(&isp->pdev->dev, "CSE boot_load timeout\n");
++		goto out_unlock;
++	}
++
++	if ((data & mask) == fail) {
++		dev_err(&isp->pdev->dev, "CSE auth failed\n");
++		ret = -EINVAL;
++		goto out_unlock;
++	}
++
++	ret = readl_poll_timeout(psys_pdata->base + BOOTLOADER_STATUS_OFFSET,
++				 data, data == BOOTLOADER_MAGIC_KEY, 500,
++				 BUTTRESS_CSE_BOOTLOAD_TIMEOUT_US);
++	if (ret) {
++		dev_err(&isp->pdev->dev, "Unexpected magic number 0x%x\n",
++			data);
++		goto out_unlock;
++	}
++
++	/*
++	 * Write authenticate_run into IU2CSEDATA0
++	 * Write sizeof(boot_load) | 0x2 << CLIENT_ID to
++	 * IU2CSEDB.IU2CSECMD and set IU2CSEDB.IU2CSEBUSY as
++	 */
++	dev_info(&isp->pdev->dev, "Sending AUTHENTICATE_RUN to CSE\n");
++	ret = ipu6_buttress_ipc_send(isp, IPU6_BUTTRESS_IPC_CSE,
++				     BUTTRESS_IU2CSEDATA0_IPC_AUTH_RUN,
++				     1, true,
++				     BUTTRESS_CSE2IUDATA0_IPC_AUTH_RUN_DONE);
++	if (ret) {
++		dev_err(&isp->pdev->dev, "CSE authenticate_run failed\n");
++		goto out_unlock;
++	}
++
++	done = BUTTRESS_SECURITY_CTL_AUTH_DONE;
++	ret = readl_poll_timeout(isp->base + BUTTRESS_REG_SECURITY_CTL, data,
++				 ((data & mask) == done ||
++				  (data & mask) == fail), 500,
++				 BUTTRESS_CSE_AUTHENTICATE_TIMEOUT_US);
++	if (ret) {
++		dev_err(&isp->pdev->dev, "CSE authenticate timeout\n");
++		goto out_unlock;
++	}
++
++	if ((data & mask) == fail) {
++		dev_err(&isp->pdev->dev, "CSE boot_load failed\n");
++		ret = -EINVAL;
++		goto out_unlock;
++	}
++
++	dev_info(&isp->pdev->dev, "CSE authenticate_run done\n");
++
++out_unlock:
++	mutex_unlock(&b->auth_mutex);
++
++	return ret;
++}
++
++static int ipu6_buttress_send_tsc_request(struct ipu6_device *isp)
++{
++	u32 val, mask, done;
++	int ret;
++
++	mask = BUTTRESS_PWR_STATE_HH_STATUS_MASK;
++
++	writel(BUTTRESS_FABRIC_CMD_START_TSC_SYNC,
++	       isp->base + BUTTRESS_REG_FABRIC_CMD);
++
++	val = readl(isp->base + BUTTRESS_REG_PWR_STATE);
++	val = FIELD_GET(mask, val);
++	if (val == BUTTRESS_PWR_STATE_HH_STATE_ERR) {
++		dev_err(&isp->pdev->dev, "Start tsc sync failed\n");
++		return -EINVAL;
++	}
++
++	done = BUTTRESS_PWR_STATE_HH_STATE_DONE;
++	ret = readl_poll_timeout(isp->base + BUTTRESS_REG_PWR_STATE, val,
++				 FIELD_GET(mask, val) == done, 500,
++				 BUTTRESS_TSC_SYNC_TIMEOUT_US);
++	if (ret)
++		dev_err(&isp->pdev->dev, "Start tsc sync timeout\n");
++
++	return ret;
++}
++
++int ipu6_buttress_start_tsc_sync(struct ipu6_device *isp)
++{
++	unsigned int i;
++
++	for (i = 0; i < BUTTRESS_TSC_SYNC_RESET_TRIAL_MAX; i++) {
++		u32 val;
++		int ret;
++
++		ret = ipu6_buttress_send_tsc_request(isp);
++		if (ret != -ETIMEDOUT)
++			return ret;
++
++		val = readl(isp->base + BUTTRESS_REG_TSW_CTL);
++		val = val | BUTTRESS_TSW_CTL_SOFT_RESET;
++		writel(val, isp->base + BUTTRESS_REG_TSW_CTL);
++		val = val & ~BUTTRESS_TSW_CTL_SOFT_RESET;
++		writel(val, isp->base + BUTTRESS_REG_TSW_CTL);
++	}
++
++	dev_err(&isp->pdev->dev, "TSC sync failed (timeout)\n");
++
++	return -ETIMEDOUT;
++}
++EXPORT_SYMBOL_NS_GPL(ipu6_buttress_start_tsc_sync, INTEL_IPU6);
++
++void ipu6_buttress_tsc_read(struct ipu6_device *isp, u64 *val)
++{
++	u32 tsc_hi_1, tsc_hi_2, tsc_lo;
++	unsigned long flags;
++
++	local_irq_save(flags);
++	tsc_hi_1 = readl(isp->base + BUTTRESS_REG_TSC_HI);
++	tsc_lo = readl(isp->base + BUTTRESS_REG_TSC_LO);
++	tsc_hi_2 = readl(isp->base + BUTTRESS_REG_TSC_HI);
++	if (tsc_hi_1 == tsc_hi_2) {
++		*val = (u64)tsc_hi_1 << 32 | tsc_lo;
++	} else {
++		/* Check if TSC has rolled over */
++		if (tsc_lo & BIT(31))
++			*val = (u64)tsc_hi_1 << 32 | tsc_lo;
++		else
++			*val = (u64)tsc_hi_2 << 32 | tsc_lo;
++	}
++	local_irq_restore(flags);
++}
++EXPORT_SYMBOL_NS_GPL(ipu6_buttress_tsc_read, INTEL_IPU6);
++
++u64 ipu6_buttress_tsc_ticks_to_ns(u64 ticks, const struct ipu6_device *isp)
++{
++	u64 ns = ticks * 10000;
++
++	/*
++	 * converting TSC tick count to ns is calculated by:
++	 * Example (TSC clock frequency is 19.2MHz):
++	 * ns = ticks * 1000 000 000 / 19.2Mhz
++	 *    = ticks * 1000 000 000 / 19200000Hz
++	 *    = ticks * 10000 / 192 ns
++	 */
++	return div_u64(ns, isp->buttress.ref_clk);
++}
++EXPORT_SYMBOL_NS_GPL(ipu6_buttress_tsc_ticks_to_ns, INTEL_IPU6);
++
++void ipu6_buttress_restore(struct ipu6_device *isp)
++{
++	struct ipu6_buttress *b = &isp->buttress;
++
++	writel(BUTTRESS_IRQS, isp->base + BUTTRESS_REG_ISR_CLEAR);
++	writel(BUTTRESS_IRQS, isp->base + BUTTRESS_REG_ISR_ENABLE);
++	writel(b->wdt_cached_value, isp->base + BUTTRESS_REG_WDT);
++}
++
++int ipu6_buttress_init(struct ipu6_device *isp)
++{
++	int ret, ipc_reset_retry = BUTTRESS_CSE_IPC_RESET_RETRY;
++	struct ipu6_buttress *b = &isp->buttress;
++	u32 val;
++
++	mutex_init(&b->power_mutex);
++	mutex_init(&b->auth_mutex);
++	mutex_init(&b->cons_mutex);
++	mutex_init(&b->ipc_mutex);
++	init_completion(&b->ish.send_complete);
++	init_completion(&b->cse.send_complete);
++	init_completion(&b->ish.recv_complete);
++	init_completion(&b->cse.recv_complete);
++
++	b->cse.nack = BUTTRESS_CSE2IUDATA0_IPC_NACK;
++	b->cse.nack_mask = BUTTRESS_CSE2IUDATA0_IPC_NACK_MASK;
++	b->cse.csr_in = BUTTRESS_REG_CSE2IUCSR;
++	b->cse.csr_out = BUTTRESS_REG_IU2CSECSR;
++	b->cse.db0_in = BUTTRESS_REG_CSE2IUDB0;
++	b->cse.db0_out = BUTTRESS_REG_IU2CSEDB0;
++	b->cse.data0_in = BUTTRESS_REG_CSE2IUDATA0;
++	b->cse.data0_out = BUTTRESS_REG_IU2CSEDATA0;
++
++	/* no ISH on IPU6 */
++	memset(&b->ish, 0, sizeof(b->ish));
++	INIT_LIST_HEAD(&b->constraints);
++
++	isp->secure_mode = ipu6_buttress_get_secure_mode(isp);
++	dev_info(&isp->pdev->dev, "IPU6 in %s mode touch 0x%x mask 0x%x\n",
++		 isp->secure_mode ? "secure" : "non-secure",
++		 readl(isp->base + BUTTRESS_REG_SECURITY_TOUCH),
++		 readl(isp->base + BUTTRESS_REG_CAMERA_MASK));
++
++	b->wdt_cached_value = readl(isp->base + BUTTRESS_REG_WDT);
++	writel(BUTTRESS_IRQS, isp->base + BUTTRESS_REG_ISR_CLEAR);
++	writel(BUTTRESS_IRQS, isp->base + BUTTRESS_REG_ISR_ENABLE);
++
++	/* get ref_clk frequency by reading the indication in btrs control */
++	val = readl(isp->base + BUTTRESS_REG_BTRS_CTRL);
++	val = FIELD_GET(BUTTRESS_REG_BTRS_CTRL_REF_CLK_IND, val);
++
++	switch (val) {
++	case 0x0:
++		b->ref_clk = 240;
++		break;
++	case 0x1:
++		b->ref_clk = 192;
++		break;
++	case 0x2:
++		b->ref_clk = 384;
++		break;
++	default:
++		dev_warn(&isp->pdev->dev,
++			 "Unsupported ref clock, use 19.2Mhz by default.\n");
++		b->ref_clk = 192;
++		break;
++	}
++
++	/* Retry couple of times in case of CSE initialization is delayed */
++	do {
++		ret = ipu6_buttress_ipc_reset(isp, &b->cse);
++		if (ret) {
++			dev_warn(&isp->pdev->dev,
++				 "IPC reset protocol failed, retrying\n");
++		} else {
++			dev_dbg(&isp->pdev->dev, "IPC reset done\n");
++			return 0;
++		}
++	} while (ipc_reset_retry--);
++
++	dev_err(&isp->pdev->dev, "IPC reset protocol failed\n");
++
++	mutex_destroy(&b->power_mutex);
++	mutex_destroy(&b->auth_mutex);
++	mutex_destroy(&b->cons_mutex);
++	mutex_destroy(&b->ipc_mutex);
++
++	return ret;
++}
++
++void ipu6_buttress_exit(struct ipu6_device *isp)
++{
++	struct ipu6_buttress *b = &isp->buttress;
++
++	writel(0, isp->base + BUTTRESS_REG_ISR_ENABLE);
++
++	mutex_destroy(&b->power_mutex);
++	mutex_destroy(&b->auth_mutex);
++	mutex_destroy(&b->cons_mutex);
++	mutex_destroy(&b->ipc_mutex);
++}
+diff --git a/drivers/media/pci/intel/ipu6/ipu6-buttress.h b/drivers/media/pci/intel/ipu6/ipu6-buttress.h
+new file mode 100644
+index 000000000000..558e1d70f4af
+--- /dev/null
++++ b/drivers/media/pci/intel/ipu6/ipu6-buttress.h
+@@ -0,0 +1,102 @@
++/* SPDX-License-Identifier: GPL-2.0-only */
++/* Copyright (C) 2013 - 2023 Intel Corporation */
++
++#ifndef IPU6_BUTTRESS_H
++#define IPU6_BUTTRESS_H
++
++#include <linux/completion.h>
++#include <linux/irqreturn.h>
++#include <linux/list.h>
++#include <linux/mutex.h>
++
++struct device;
++struct firmware;
++struct ipu6_device;
++struct ipu6_bus_device;
++
++#define BUTTRESS_PS_FREQ_STEP		25U
++#define BUTTRESS_MIN_FORCE_PS_FREQ	(BUTTRESS_PS_FREQ_STEP * 8)
++#define BUTTRESS_MAX_FORCE_PS_FREQ	(BUTTRESS_PS_FREQ_STEP * 32)
++
++#define BUTTRESS_IS_FREQ_STEP		25U
++#define BUTTRESS_MIN_FORCE_IS_FREQ	(BUTTRESS_IS_FREQ_STEP * 8)
++#define BUTTRESS_MAX_FORCE_IS_FREQ	(BUTTRESS_IS_FREQ_STEP * 22)
++
++struct ipu6_buttress_ctrl {
++	u32 freq_ctl, pwr_sts_shift, pwr_sts_mask, pwr_sts_on, pwr_sts_off;
++	unsigned int ratio;
++	unsigned int qos_floor;
++	bool started;
++};
++
++struct ipu6_buttress_ipc {
++	struct completion send_complete;
++	struct completion recv_complete;
++	u32 nack;
++	u32 nack_mask;
++	u32 recv_data;
++	u32 csr_out;
++	u32 csr_in;
++	u32 db0_in;
++	u32 db0_out;
++	u32 data0_out;
++	u32 data0_in;
++};
++
++struct ipu6_buttress {
++	struct mutex power_mutex, auth_mutex, cons_mutex, ipc_mutex;
++	struct ipu6_buttress_ipc cse;
++	struct ipu6_buttress_ipc ish;
++	struct list_head constraints;
++	u32 wdt_cached_value;
++	bool force_suspend;
++	u32 ref_clk;
++};
++
++struct ipu6_buttress_sensor_clk_freq {
++	unsigned int rate;
++	unsigned int val;
++};
++
++enum ipu6_buttress_ipc_domain {
++	IPU6_BUTTRESS_IPC_CSE,
++	IPU6_BUTTRESS_IPC_ISH,
++};
++
++struct ipu6_buttress_constraint {
++	struct list_head list;
++	unsigned int min_freq;
++};
++
++struct ipu6_ipc_buttress_bulk_msg {
++	u32 cmd;
++	u32 expected_resp;
++	bool require_resp;
++	u8 cmd_size;
++};
++
++int ipu6_buttress_ipc_reset(struct ipu6_device *isp,
++			    struct ipu6_buttress_ipc *ipc);
++int ipu6_buttress_map_fw_image(struct ipu6_bus_device *sys,
++			       const struct firmware *fw,
++			       struct sg_table *sgt);
++void ipu6_buttress_unmap_fw_image(struct ipu6_bus_device *sys,
++				  struct sg_table *sgt);
++int ipu6_buttress_power(struct device *dev, struct ipu6_buttress_ctrl *ctrl,
++			bool on);
++bool ipu6_buttress_get_secure_mode(struct ipu6_device *isp);
++int ipu6_buttress_authenticate(struct ipu6_device *isp);
++int ipu6_buttress_reset_authentication(struct ipu6_device *isp);
++bool ipu6_buttress_auth_done(struct ipu6_device *isp);
++int ipu6_buttress_start_tsc_sync(struct ipu6_device *isp);
++void ipu6_buttress_tsc_read(struct ipu6_device *isp, u64 *val);
++u64 ipu6_buttress_tsc_ticks_to_ns(u64 ticks, const struct ipu6_device *isp);
++
++irqreturn_t ipu6_buttress_isr(int irq, void *isp_ptr);
++irqreturn_t ipu6_buttress_isr_threaded(int irq, void *isp_ptr);
++int ipu6_buttress_init(struct ipu6_device *isp);
++void ipu6_buttress_exit(struct ipu6_device *isp);
++void ipu6_buttress_csi_port_config(struct ipu6_device *isp,
++				   u32 legacy, u32 combo);
++void ipu6_buttress_restore(struct ipu6_device *isp);
++#endif /* IPU6_BUTTRESS_H */
+diff --git a/drivers/media/pci/intel/ipu6/ipu6-platform-buttress-regs.h b/drivers/media/pci/intel/ipu6/ipu6-platform-buttress-regs.h
+new file mode 100644
+index 000000000000..87239af96502
+--- /dev/null
++++ b/drivers/media/pci/intel/ipu6/ipu6-platform-buttress-regs.h
+@@ -0,0 +1,232 @@
++/* SPDX-License-Identifier: GPL-2.0-only */
++/* Copyright (C) 2023 Intel Corporation */
++
++#ifndef IPU6_PLATFORM_BUTTRESS_REGS_H
++#define IPU6_PLATFORM_BUTTRESS_REGS_H
++
++#include <linux/bits.h>
++
++/* IS_WORKPOINT_REQ */
++#define IPU6_BUTTRESS_REG_IS_FREQ_CTL		0x34
++/* PS_WORKPOINT_REQ */
++#define IPU6_BUTTRESS_REG_PS_FREQ_CTL		0x38
++
++#define IPU6_IS_FREQ_MAX		533
++#define IPU6_IS_FREQ_MIN		200
++#define IPU6_PS_FREQ_MAX		450
++#define IPU6_IS_FREQ_RATIO_BASE		25
++#define IPU6_PS_FREQ_RATIO_BASE		25
++
++/* should be tuned for real silicon */
++#define IPU6_IS_FREQ_CTL_DEFAULT_RATIO		0x08
++#define IPU6SE_IS_FREQ_CTL_DEFAULT_RATIO	0x0a
++#define IPU6_PS_FREQ_CTL_DEFAULT_RATIO		0x0d
++
++#define IPU6_IS_FREQ_CTL_DEFAULT_QOS_FLOOR_RATIO	0x10
++#define IPU6_PS_FREQ_CTL_DEFAULT_QOS_FLOOR_RATIO	0x0708
++
++#define IPU6_BUTTRESS_PWR_STATE_IS_PWR_SHIFT	3
++#define IPU6_BUTTRESS_PWR_STATE_IS_PWR_MASK	GENMASK(4, 3)
++
++#define IPU6_BUTTRESS_PWR_STATE_PS_PWR_SHIFT	6
++#define IPU6_BUTTRESS_PWR_STATE_PS_PWR_MASK	GENMASK(7, 6)
++
++#define IPU6_BUTTRESS_PWR_STATE_DN_DONE		0x0
++#define IPU6_BUTTRESS_PWR_STATE_UP_PROCESS	0x1
++#define IPU6_BUTTRESS_PWR_STATE_DN_PROCESS	0x2
++#define IPU6_BUTTRESS_PWR_STATE_UP_DONE		0x3
++
++#define IPU6_BUTTRESS_REG_FPGA_SUPPORT_0	0x270
++#define IPU6_BUTTRESS_REG_FPGA_SUPPORT_1	0x274
++#define IPU6_BUTTRESS_REG_FPGA_SUPPORT_2	0x278
++#define IPU6_BUTTRESS_REG_FPGA_SUPPORT_3	0x27c
++#define IPU6_BUTTRESS_REG_FPGA_SUPPORT_4	0x280
++#define IPU6_BUTTRESS_REG_FPGA_SUPPORT_5	0x284
++#define IPU6_BUTTRESS_REG_FPGA_SUPPORT_6	0x288
++#define IPU6_BUTTRESS_REG_FPGA_SUPPORT_7	0x28c
++
++#define BUTTRESS_REG_WDT			0x8
++#define BUTTRESS_REG_BTRS_CTRL			0xc
++#define BUTTRESS_REG_BTRS_CTRL_STALL_MODE_VC0	BIT(0)
++#define BUTTRESS_REG_BTRS_CTRL_STALL_MODE_VC1	BIT(1)
++#define BUTTRESS_REG_BTRS_CTRL_REF_CLK_IND	GENMASK(9, 8)
++
++#define BUTTRESS_REG_FW_RESET_CTL	0x30
++#define BUTTRESS_FW_RESET_CTL_START	BIT(0)
++#define BUTTRESS_FW_RESET_CTL_DONE	BIT(1)
++
++#define BUTTRESS_REG_IS_FREQ_CTL	0x34
++#define BUTTRESS_REG_PS_FREQ_CTL	0x38
++
++#define BUTTRESS_FREQ_CTL_START		BIT(31)
++#define BUTTRESS_FREQ_CTL_ICCMAX_LEVEL		GENMASK(19, 16)
++#define BUTTRESS_FREQ_CTL_QOS_FLOOR_MASK	GENMASK(15, 8)
++#define BUTTRESS_FREQ_CTL_RATIO_MASK	GENMASK(7, 0)
++
++#define BUTTRESS_REG_PWR_STATE	0x5c
++
++#define BUTTRESS_PWR_STATE_RESET		0x0
++#define BUTTRESS_PWR_STATE_PWR_ON_DONE		0x1
++#define BUTTRESS_PWR_STATE_PWR_RDY		0x3
++#define BUTTRESS_PWR_STATE_PWR_IDLE		0x4
++
++#define BUTTRESS_PWR_STATE_HH_STATUS_MASK	GENMASK(12, 11)
++
++enum {
++	BUTTRESS_PWR_STATE_HH_STATE_IDLE,
++	BUTTRESS_PWR_STATE_HH_STATE_IN_PRGS,
++	BUTTRESS_PWR_STATE_HH_STATE_DONE,
++	BUTTRESS_PWR_STATE_HH_STATE_ERR,
++};
++
++#define BUTTRESS_PWR_STATE_IS_PWR_FSM_MASK	GENMASK(23, 19)
++
++#define BUTTRESS_PWR_STATE_IS_PWR_FSM_IDLE			0x0
++#define BUTTRESS_PWR_STATE_IS_PWR_FSM_WAIT_4_PLL_CMP		0x1
++#define BUTTRESS_PWR_STATE_IS_PWR_FSM_WAIT_4_CLKACK		0x2
++#define BUTTRESS_PWR_STATE_IS_PWR_FSM_WAIT_4_PG_ACK		0x3
++#define BUTTRESS_PWR_STATE_IS_PWR_FSM_RST_ASSRT_CYCLES		0x4
++#define BUTTRESS_PWR_STATE_IS_PWR_FSM_STOP_CLK_CYCLES1		0x5
++#define BUTTRESS_PWR_STATE_IS_PWR_FSM_STOP_CLK_CYCLES2		0x6
++#define BUTTRESS_PWR_STATE_IS_PWR_FSM_RST_DEASSRT_CYCLES	0x7
++#define BUTTRESS_PWR_STATE_IS_PWR_FSM_WAIT_4_FUSE_WR_CMP	0x8
++#define BUTTRESS_PWR_STATE_IS_PWR_FSM_BRK_POINT			0x9
++#define BUTTRESS_PWR_STATE_IS_PWR_FSM_IS_RDY			0xa
++#define BUTTRESS_PWR_STATE_IS_PWR_FSM_HALT_HALTED		0xb
++#define BUTTRESS_PWR_STATE_IS_PWR_FSM_RST_DURATION_CNT3		0xc
++#define BUTTRESS_PWR_STATE_IS_PWR_FSM_WAIT_4_CLKACK_PD		0xd
++#define BUTTRESS_PWR_STATE_IS_PWR_FSM_PD_BRK_POINT		0xe
++#define BUTTRESS_PWR_STATE_IS_PWR_FSM_WAIT_4_PD_PG_ACK0		0xf
++
++#define BUTTRESS_PWR_STATE_PS_PWR_FSM_MASK	GENMASK(28, 24)
++
++#define BUTTRESS_PWR_STATE_PS_PWR_FSM_IDLE			0x0
++#define BUTTRESS_PWR_STATE_PS_PWR_FSM_WAIT_PU_PLL_IP_RDY	0x1
++#define BUTTRESS_PWR_STATE_PS_PWR_FSM_WAIT_RO_PRE_CNT_EXH	0x2
++#define BUTTRESS_PWR_STATE_PS_PWR_FSM_WAIT_PU_VGI_PWRGOOD	0x3
++#define BUTTRESS_PWR_STATE_PS_PWR_FSM_WAIT_RO_POST_CNT_EXH	0x4
++#define BUTTRESS_PWR_STATE_PS_PWR_FSM_WR_PLL_RATIO		0x5
++#define BUTTRESS_PWR_STATE_PS_PWR_FSM_WAIT_PU_PLL_CMP		0x6
++#define BUTTRESS_PWR_STATE_PS_PWR_FSM_WAIT_PU_CLKACK		0x7
++#define BUTTRESS_PWR_STATE_PS_PWR_FSM_RST_ASSRT_CYCLES		0x8
++#define BUTTRESS_PWR_STATE_PS_PWR_FSM_STOP_CLK_CYCLES1		0x9
++#define BUTTRESS_PWR_STATE_PS_PWR_FSM_STOP_CLK_CYCLES2		0xa
++#define BUTTRESS_PWR_STATE_PS_PWR_FSM_RST_DEASSRT_CYCLES	0xb
++#define BUTTRESS_PWR_STATE_PS_PWR_FSM_PU_BRK_PNT		0xc
++#define BUTTRESS_PWR_STATE_PS_PWR_FSM_WAIT_FUSE_ACCPT		0xd
++#define BUTTRESS_PWR_STATE_PS_PWR_FSM_PS_PWR_UP			0xf
++#define BUTTRESS_PWR_STATE_PS_PWR_FSM_WAIT_4_HALTED		0x10
++#define BUTTRESS_PWR_STATE_PS_PWR_FSM_RESET_CNT3		0x11
++#define BUTTRESS_PWR_STATE_PS_PWR_FSM_WAIT_PD_CLKACK		0x12
++#define BUTTRESS_PWR_STATE_PS_PWR_FSM_WAIT_PD_OFF_IND		0x13
++#define BUTTRESS_PWR_STATE_PS_PWR_FSM_WAIT_DVFS_PH4		0x14
++#define BUTTRESS_PWR_STATE_PS_PWR_FSM_WAIT_DVFS_PLL_CMP		0x15
++#define BUTTRESS_PWR_STATE_PS_PWR_FSM_WAIT_DVFS_CLKACK		0x16
++
++#define BUTTRESS_REG_SECURITY_CTL	0x300
++#define BUTTRESS_REG_SKU		0x314
++#define BUTTRESS_REG_SECURITY_TOUCH	0x318
++#define BUTTRESS_REG_CAMERA_MASK	0x84
++
++#define BUTTRESS_SECURITY_CTL_FW_SECURE_MODE	BIT(16)
++#define BUTTRESS_SECURITY_CTL_FW_SETUP_MASK	GENMASK(4, 0)
++
++#define BUTTRESS_SECURITY_CTL_FW_SETUP_DONE		BIT(0)
++#define BUTTRESS_SECURITY_CTL_AUTH_DONE			BIT(1)
++#define BUTTRESS_SECURITY_CTL_AUTH_FAILED		BIT(3)
++
++#define BUTTRESS_REG_FW_SOURCE_BASE_LO	0x78
++#define BUTTRESS_REG_FW_SOURCE_BASE_HI	0x7C
++#define BUTTRESS_REG_FW_SOURCE_SIZE	0x80
++
++#define BUTTRESS_REG_ISR_STATUS		0x90
++#define BUTTRESS_REG_ISR_ENABLED_STATUS	0x94
++#define BUTTRESS_REG_ISR_ENABLE		0x98
++#define BUTTRESS_REG_ISR_CLEAR		0x9C
++
++#define BUTTRESS_ISR_IS_IRQ			BIT(0)
++#define BUTTRESS_ISR_PS_IRQ			BIT(1)
++#define BUTTRESS_ISR_IPC_EXEC_DONE_BY_CSE	BIT(2)
++#define BUTTRESS_ISR_IPC_EXEC_DONE_BY_ISH	BIT(3)
++#define BUTTRESS_ISR_IPC_FROM_CSE_IS_WAITING	BIT(4)
++#define BUTTRESS_ISR_IPC_FROM_ISH_IS_WAITING	BIT(5)
++#define BUTTRESS_ISR_CSE_CSR_SET		BIT(6)
++#define BUTTRESS_ISR_ISH_CSR_SET		BIT(7)
++#define BUTTRESS_ISR_SPURIOUS_CMP		BIT(8)
++#define BUTTRESS_ISR_WATCHDOG_EXPIRED		BIT(9)
++#define BUTTRESS_ISR_PUNIT_2_IUNIT_IRQ		BIT(10)
++#define BUTTRESS_ISR_SAI_VIOLATION		BIT(11)
++#define BUTTRESS_ISR_HW_ASSERTION		BIT(12)
++#define BUTTRESS_ISR_IS_CORRECTABLE_MEM_ERR	BIT(13)
++#define BUTTRESS_ISR_IS_FATAL_MEM_ERR		BIT(14)
++#define BUTTRESS_ISR_IS_NON_FATAL_MEM_ERR	BIT(15)
++#define BUTTRESS_ISR_PS_CORRECTABLE_MEM_ERR	BIT(16)
++#define BUTTRESS_ISR_PS_FATAL_MEM_ERR		BIT(17)
++#define BUTTRESS_ISR_PS_NON_FATAL_MEM_ERR	BIT(18)
++#define BUTTRESS_ISR_PS_FAST_THROTTLE		BIT(19)
++#define BUTTRESS_ISR_UFI_ERROR			BIT(20)
++
++#define BUTTRESS_REG_IU2CSEDB0	0x100
++
++#define BUTTRESS_IU2CSEDB0_BUSY		BIT(31)
++#define BUTTRESS_IU2CSEDB0_IPC_CLIENT_ID_VAL	2
++
++#define BUTTRESS_REG_IU2CSEDATA0	0x104
++
++#define BUTTRESS_IU2CSEDATA0_IPC_BOOT_LOAD		1
++#define BUTTRESS_IU2CSEDATA0_IPC_AUTH_RUN		2
++#define BUTTRESS_IU2CSEDATA0_IPC_AUTH_REPLACE		3
++#define BUTTRESS_IU2CSEDATA0_IPC_UPDATE_SECURE_TOUCH	16
++
++#define BUTTRESS_CSE2IUDATA0_IPC_BOOT_LOAD_DONE			BIT(0)
++#define BUTTRESS_CSE2IUDATA0_IPC_AUTH_RUN_DONE			BIT(1)
++#define BUTTRESS_CSE2IUDATA0_IPC_AUTH_REPLACE_DONE		BIT(2)
++#define BUTTRESS_CSE2IUDATA0_IPC_UPDATE_SECURE_TOUCH_DONE	BIT(4)
++
++#define BUTTRESS_REG_IU2CSECSR		0x108
++
++#define BUTTRESS_IU2CSECSR_IPC_PEER_COMP_ACTIONS_RST_PHASE1		BIT(0)
++#define BUTTRESS_IU2CSECSR_IPC_PEER_COMP_ACTIONS_RST_PHASE2		BIT(1)
++#define BUTTRESS_IU2CSECSR_IPC_PEER_QUERIED_IP_COMP_ACTIONS_RST_PHASE	BIT(2)
++#define BUTTRESS_IU2CSECSR_IPC_PEER_ASSERTED_REG_VALID_REQ		BIT(3)
++#define BUTTRESS_IU2CSECSR_IPC_PEER_ACKED_REG_VALID			BIT(4)
++#define BUTTRESS_IU2CSECSR_IPC_PEER_DEASSERTED_REG_VALID_REQ		BIT(5)
++
++#define BUTTRESS_REG_CSE2IUDB0		0x304
++#define BUTTRESS_REG_CSE2IUCSR		0x30C
++#define BUTTRESS_REG_CSE2IUDATA0	0x308
++
++/* 0x20 == NACK, 0xf == unknown command */
++#define BUTTRESS_CSE2IUDATA0_IPC_NACK      0xf20
++#define BUTTRESS_CSE2IUDATA0_IPC_NACK_MASK GENMASK(15, 0)
++
++#define BUTTRESS_REG_ISH2IUCSR		0x50
++#define BUTTRESS_REG_ISH2IUDB0		0x54
++#define BUTTRESS_REG_ISH2IUDATA0	0x58
++
++#define BUTTRESS_REG_IU2ISHDB0		0x10C
++#define BUTTRESS_REG_IU2ISHDATA0	0x110
++#define BUTTRESS_REG_IU2ISHDATA1	0x114
++#define BUTTRESS_REG_IU2ISHCSR		0x118
++
++#define BUTTRESS_REG_FABRIC_CMD		0x88
++
++#define BUTTRESS_FABRIC_CMD_START_TSC_SYNC	BIT(0)
++#define BUTTRESS_FABRIC_CMD_IS_DRAIN		BIT(4)
++
++#define BUTTRESS_REG_TSW_CTL		0x120
++#define BUTTRESS_TSW_CTL_SOFT_RESET	BIT(8)
++
++#define BUTTRESS_REG_TSC_LO	0x164
++#define BUTTRESS_REG_TSC_HI	0x168
++
++#define BUTTRESS_IRQS		(BUTTRESS_ISR_IPC_FROM_CSE_IS_WAITING | \
++				 BUTTRESS_ISR_IPC_EXEC_DONE_BY_CSE |    \
++				 BUTTRESS_ISR_IS_IRQ | BUTTRESS_ISR_PS_IRQ)
++
++#define BUTTRESS_EVENT		 (BUTTRESS_ISR_IPC_FROM_CSE_IS_WAITING | \
++				  BUTTRESS_ISR_IPC_FROM_ISH_IS_WAITING | \
++				  BUTTRESS_ISR_IPC_EXEC_DONE_BY_CSE |    \
++				  BUTTRESS_ISR_IPC_EXEC_DONE_BY_ISH |    \
++				  BUTTRESS_ISR_SAI_VIOLATION)
++#endif /* IPU6_PLATFORM_BUTTRESS_REGS_H */
+-- 
+2.43.2
+
+
+From 12bd5bdd53a7c829cc5cd61a6887f89ffb036f8f Mon Sep 17 00:00:00 2001
+From: Bingbu Cao <bingbu.cao@intel.com>
+Date: Thu, 11 Jan 2024 14:55:18 +0800
+Subject: [PATCH 11/33] media: intel/ipu6: CPD parsing for get firmware
+ components
+
+For IPU6, firmware is generated and released as signed
+Code Partition Directory (CPD) format file, which is aligned with
+the SPI flash code partition definition. CPD format include CPD
+header, manifest, metadata and module data. Driver can parse them
+according to the CPD layout to acquire each component.
+
+Signed-off-by: Bingbu Cao <bingbu.cao@intel.com>
+---
+ drivers/media/pci/intel/ipu6/ipu6-cpd.c | 362 ++++++++++++++++++++++++
+ drivers/media/pci/intel/ipu6/ipu6-cpd.h | 105 +++++++
+ 2 files changed, 467 insertions(+)
+ create mode 100644 drivers/media/pci/intel/ipu6/ipu6-cpd.c
+ create mode 100644 drivers/media/pci/intel/ipu6/ipu6-cpd.h
+
+diff --git a/drivers/media/pci/intel/ipu6/ipu6-cpd.c b/drivers/media/pci/intel/ipu6/ipu6-cpd.c
+new file mode 100644
+index 000000000000..b0ffd04c4cd3
+--- /dev/null
++++ b/drivers/media/pci/intel/ipu6/ipu6-cpd.c
+@@ -0,0 +1,362 @@
++// SPDX-License-Identifier: GPL-2.0-only
++/*
++ * Copyright (C) 2013 - 2023 Intel Corporation
++ */
++
++#include <linux/bitfield.h>
++#include <linux/bits.h>
++#include <linux/err.h>
++#include <linux/dma-mapping.h>
++#include <linux/gfp_types.h>
++#include <linux/math64.h>
++#include <linux/sizes.h>
++#include <linux/types.h>
++
++#include "ipu6.h"
++#include "ipu6-bus.h"
++#include "ipu6-cpd.h"
++
++/* 15 entries + header*/
++#define MAX_PKG_DIR_ENT_CNT		16
++/* 2 qword per entry/header */
++#define PKG_DIR_ENT_LEN			2
++/* PKG_DIR size in bytes */
++#define PKG_DIR_SIZE			((MAX_PKG_DIR_ENT_CNT) *	\
++					 (PKG_DIR_ENT_LEN) * sizeof(u64))
++/* _IUPKDR_ */
++#define PKG_DIR_HDR_MARK		0x5f4955504b44525fULL
++
++/* $CPD */
++#define CPD_HDR_MARK			0x44504324
++
++#define MAX_MANIFEST_SIZE		(SZ_2K * sizeof(u32))
++#define MAX_METADATA_SIZE		SZ_64K
++
++#define MAX_COMPONENT_ID		127
++#define MAX_COMPONENT_VERSION		0xffff
++
++#define MANIFEST_IDX	0
++#define METADATA_IDX	1
++#define MODULEDATA_IDX	2
++/*
++ * PKG_DIR Entry (type == id)
++ * 63:56        55      54:48   47:32   31:24   23:0
++ * Rsvd         Rsvd    Type    Version Rsvd    Size
++ */
++#define PKG_DIR_SIZE_MASK	GENMASK(23, 0)
++#define PKG_DIR_VERSION_MASK	GENMASK(47, 32)
++#define PKG_DIR_TYPE_MASK	GENMASK(54, 48)
++
++static inline const struct ipu6_cpd_ent *ipu6_cpd_get_entry(const void *cpd,
++							    u8 idx)
++{
++	const struct ipu6_cpd_hdr *cpd_hdr = cpd;
++	const struct ipu6_cpd_ent *ent;
++
++	ent = (const struct ipu6_cpd_ent *)((const u8 *)cpd + cpd_hdr->hdr_len);
++	return ent + idx;
++}
++
++#define ipu6_cpd_get_manifest(cpd) ipu6_cpd_get_entry(cpd, MANIFEST_IDX)
++#define ipu6_cpd_get_metadata(cpd) ipu6_cpd_get_entry(cpd, METADATA_IDX)
++#define ipu6_cpd_get_moduledata(cpd) ipu6_cpd_get_entry(cpd, MODULEDATA_IDX)
++
++static const struct ipu6_cpd_metadata_cmpnt_hdr *
++ipu6_cpd_metadata_get_cmpnt(struct ipu6_device *isp, const void *metadata,
++			    unsigned int metadata_size, u8 idx)
++{
++	size_t extn_size = sizeof(struct ipu6_cpd_metadata_extn);
++	size_t cmpnt_count = metadata_size - extn_size;
++
++	cmpnt_count = div_u64(cmpnt_count, isp->cpd_metadata_cmpnt_size);
++
++	if (idx > MAX_COMPONENT_ID || idx >= cmpnt_count) {
++		dev_err(&isp->pdev->dev, "Component index out of range (%d)\n",
++			idx);
++		return ERR_PTR(-EINVAL);
++	}
++
++	return metadata + extn_size + idx * isp->cpd_metadata_cmpnt_size;
++}
++
++static u32 ipu6_cpd_metadata_cmpnt_version(struct ipu6_device *isp,
++					   const void *metadata,
++					   unsigned int metadata_size, u8 idx)
++{
++	const struct ipu6_cpd_metadata_cmpnt_hdr *cmpnt;
++
++	cmpnt = ipu6_cpd_metadata_get_cmpnt(isp, metadata, metadata_size, idx);
++	if (IS_ERR(cmpnt))
++		return PTR_ERR(cmpnt);
++
++	return cmpnt->ver;
++}
++
++static int ipu6_cpd_metadata_get_cmpnt_id(struct ipu6_device *isp,
++					  const void *metadata,
++					  unsigned int metadata_size, u8 idx)
++{
++	const struct ipu6_cpd_metadata_cmpnt_hdr *cmpnt;
++
++	cmpnt = ipu6_cpd_metadata_get_cmpnt(isp, metadata, metadata_size, idx);
++	if (IS_ERR(cmpnt))
++		return PTR_ERR(cmpnt);
++
++	return cmpnt->id;
++}
++
++static int ipu6_cpd_parse_module_data(struct ipu6_device *isp,
++				      const void *module_data,
++				      unsigned int module_data_size,
++				      dma_addr_t dma_addr_module_data,
++				      u64 *pkg_dir, const void *metadata,
++				      unsigned int metadata_size)
++{
++	const struct ipu6_cpd_module_data_hdr *module_data_hdr;
++	const struct ipu6_cpd_hdr *dir_hdr;
++	const struct ipu6_cpd_ent *dir_ent;
++	unsigned int i;
++	u8 len;
++
++	if (!module_data)
++		return -EINVAL;
++
++	module_data_hdr = module_data;
++	dir_hdr = module_data + module_data_hdr->hdr_len;
++	len = dir_hdr->hdr_len;
++	dir_ent = (const struct ipu6_cpd_ent *)(((u8 *)dir_hdr) + len);
++
++	pkg_dir[0] = PKG_DIR_HDR_MARK;
++	/* pkg_dir entry count = component count + pkg_dir header */
++	pkg_dir[1] = dir_hdr->ent_cnt + 1;
++
++	for (i = 0; i < dir_hdr->ent_cnt; i++, dir_ent++) {
++		u64 *p = &pkg_dir[PKG_DIR_ENT_LEN *  (1 + i)];
++		int ver, id;
++
++		*p++ = dma_addr_module_data + dir_ent->offset;
++		id = ipu6_cpd_metadata_get_cmpnt_id(isp, metadata,
++						    metadata_size, i);
++		if (id < 0 || id > MAX_COMPONENT_ID) {
++			dev_err(&isp->pdev->dev, "Invalid CPD component id\n");
++			return -EINVAL;
++		}
++
++		ver = ipu6_cpd_metadata_cmpnt_version(isp, metadata,
++						      metadata_size, i);
++		if (ver < 0 || ver > MAX_COMPONENT_VERSION) {
++			dev_err(&isp->pdev->dev,
++				"Invalid CPD component version\n");
++			return -EINVAL;
++		}
++
++		*p = FIELD_PREP(PKG_DIR_SIZE_MASK, dir_ent->len) |
++			FIELD_PREP(PKG_DIR_TYPE_MASK, id) |
++			FIELD_PREP(PKG_DIR_VERSION_MASK, ver);
++	}
++
++	return 0;
++}
++
++int ipu6_cpd_create_pkg_dir(struct ipu6_bus_device *adev, const void *src)
++{
++	dma_addr_t dma_addr_src = sg_dma_address(adev->fw_sgt.sgl);
++	const struct ipu6_cpd_ent *ent, *man_ent, *met_ent;
++	struct device *dev = &adev->auxdev.dev;
++	struct ipu6_device *isp = adev->isp;
++	unsigned int man_sz, met_sz;
++	void *pkg_dir_pos;
++	int ret;
++
++	man_ent = ipu6_cpd_get_manifest(src);
++	man_sz = man_ent->len;
++
++	met_ent = ipu6_cpd_get_metadata(src);
++	met_sz = met_ent->len;
++
++	adev->pkg_dir_size = PKG_DIR_SIZE + man_sz + met_sz;
++	adev->pkg_dir = dma_alloc_attrs(dev, adev->pkg_dir_size,
++					&adev->pkg_dir_dma_addr, GFP_KERNEL, 0);
++	if (!adev->pkg_dir)
++		return -ENOMEM;
++
++	/*
++	 * pkg_dir entry/header:
++	 * qword | 63:56 | 55   | 54:48 | 47:32 | 31:24 | 23:0
++	 * N         Address/Offset/"_IUPKDR_"
++	 * N + 1 | rsvd  | rsvd | type  | ver   | rsvd  | size
++	 *
++	 * We can ignore other fields that size in N + 1 qword as they
++	 * are 0 anyway. Just setting size for now.
++	 */
++
++	ent = ipu6_cpd_get_moduledata(src);
++
++	ret = ipu6_cpd_parse_module_data(isp, src + ent->offset,
++					 ent->len, dma_addr_src + ent->offset,
++					 adev->pkg_dir, src + met_ent->offset,
++					 met_ent->len);
++	if (ret) {
++		dev_err(&isp->pdev->dev, "Failed to parse module data\n");
++		dma_free_attrs(dev, adev->pkg_dir_size,
++			       adev->pkg_dir, adev->pkg_dir_dma_addr, 0);
++		return ret;
++	}
++
++	/* Copy manifest after pkg_dir */
++	pkg_dir_pos = adev->pkg_dir + PKG_DIR_ENT_LEN * MAX_PKG_DIR_ENT_CNT;
++	memcpy(pkg_dir_pos, src + man_ent->offset, man_sz);
++
++	/* Copy metadata after manifest */
++	pkg_dir_pos += man_sz;
++	memcpy(pkg_dir_pos, src + met_ent->offset, met_sz);
++
++	dma_sync_single_range_for_device(dev, adev->pkg_dir_dma_addr,
++					 0, adev->pkg_dir_size, DMA_TO_DEVICE);
++
++	return 0;
++}
++EXPORT_SYMBOL_NS_GPL(ipu6_cpd_create_pkg_dir, INTEL_IPU6);
++
++void ipu6_cpd_free_pkg_dir(struct ipu6_bus_device *adev)
++{
++	dma_free_attrs(&adev->auxdev.dev, adev->pkg_dir_size, adev->pkg_dir,
++		       adev->pkg_dir_dma_addr, 0);
++}
++EXPORT_SYMBOL_NS_GPL(ipu6_cpd_free_pkg_dir, INTEL_IPU6);
++
++static int ipu6_cpd_validate_cpd(struct ipu6_device *isp, const void *cpd,
++				 unsigned long cpd_size,
++				 unsigned long data_size)
++{
++	const struct ipu6_cpd_hdr *cpd_hdr = cpd;
++	const struct ipu6_cpd_ent *ent;
++	unsigned int i;
++	u8 len;
++
++	len = cpd_hdr->hdr_len;
++
++	/* Ensure cpd hdr is within moduledata */
++	if (cpd_size < len) {
++		dev_err(&isp->pdev->dev, "Invalid CPD moduledata size\n");
++		return -EINVAL;
++	}
++
++	/* Sanity check for CPD header */
++	if ((cpd_size - len) / sizeof(*ent) < cpd_hdr->ent_cnt) {
++		dev_err(&isp->pdev->dev, "Invalid CPD header\n");
++		return -EINVAL;
++	}
++
++	/* Ensure that all entries are within moduledata */
++	ent = (const struct ipu6_cpd_ent *)(((const u8 *)cpd_hdr) + len);
++	for (i = 0; i < cpd_hdr->ent_cnt; i++, ent++) {
++		if (data_size < ent->offset ||
++		    data_size - ent->offset < ent->len) {
++			dev_err(&isp->pdev->dev, "Invalid CPD entry (%d)\n", i);
++			return -EINVAL;
++		}
++	}
++
++	return 0;
++}
++
++static int ipu6_cpd_validate_moduledata(struct ipu6_device *isp,
++					const void *moduledata,
++					u32 moduledata_size)
++{
++	const struct ipu6_cpd_module_data_hdr *mod_hdr = moduledata;
++	int ret;
++
++	/* Ensure moduledata hdr is within moduledata */
++	if (moduledata_size < sizeof(*mod_hdr) ||
++	    moduledata_size < mod_hdr->hdr_len) {
++		dev_err(&isp->pdev->dev, "Invalid CPD moduledata size\n");
++		return -EINVAL;
++	}
++
++	dev_info(&isp->pdev->dev, "FW version: %x\n", mod_hdr->fw_pkg_date);
++	ret = ipu6_cpd_validate_cpd(isp, moduledata + mod_hdr->hdr_len,
++				    moduledata_size - mod_hdr->hdr_len,
++				    moduledata_size);
++	if (ret) {
++		dev_err(&isp->pdev->dev, "Invalid CPD in moduledata\n");
++		return ret;
++	}
++
++	return 0;
++}
++
++static int ipu6_cpd_validate_metadata(struct ipu6_device *isp,
++				      const void *metadata, u32 meta_size)
++{
++	const struct ipu6_cpd_metadata_extn *extn = metadata;
++
++	/* Sanity check for metadata size */
++	if (meta_size < sizeof(*extn) || meta_size > MAX_METADATA_SIZE) {
++		dev_err(&isp->pdev->dev, "Invalid CPD metadata\n");
++		return -EINVAL;
++	}
++
++	/* Validate extension and image types */
++	if (extn->extn_type != IPU6_CPD_METADATA_EXTN_TYPE_IUNIT ||
++	    extn->img_type != IPU6_CPD_METADATA_IMAGE_TYPE_MAIN_FIRMWARE) {
++		dev_err(&isp->pdev->dev,
++			"Invalid CPD metadata descriptor img_type (%d)\n",
++			extn->img_type);
++		return -EINVAL;
++	}
++
++	/* Validate metadata size multiple of metadata components */
++	if ((meta_size - sizeof(*extn)) % isp->cpd_metadata_cmpnt_size) {
++		dev_err(&isp->pdev->dev, "Invalid CPD metadata size\n");
++		return -EINVAL;
++	}
++
++	return 0;
++}
++
++int ipu6_cpd_validate_cpd_file(struct ipu6_device *isp, const void *cpd_file,
++			       unsigned long cpd_file_size)
++{
++	const struct ipu6_cpd_hdr *hdr = cpd_file;
++	const struct ipu6_cpd_ent *ent;
++	int ret;
++
++	ret = ipu6_cpd_validate_cpd(isp, cpd_file, cpd_file_size,
++				    cpd_file_size);
++	if (ret) {
++		dev_err(&isp->pdev->dev, "Invalid CPD in file\n");
++		return ret;
++	}
++
++	/* Check for CPD file marker */
++	if (hdr->hdr_mark != CPD_HDR_MARK) {
++		dev_err(&isp->pdev->dev, "Invalid CPD header\n");
++		return -EINVAL;
++	}
++
++	/* Sanity check for manifest size */
++	ent = ipu6_cpd_get_manifest(cpd_file);
++	if (ent->len > MAX_MANIFEST_SIZE) {
++		dev_err(&isp->pdev->dev, "Invalid CPD manifest size\n");
++		return -EINVAL;
++	}
++
++	/* Validate metadata */
++	ent = ipu6_cpd_get_metadata(cpd_file);
++	ret = ipu6_cpd_validate_metadata(isp, cpd_file + ent->offset, ent->len);
++	if (ret) {
++		dev_err(&isp->pdev->dev, "Invalid CPD metadata\n");
++		return ret;
++	}
++
++	/* Validate moduledata */
++	ent = ipu6_cpd_get_moduledata(cpd_file);
++	ret = ipu6_cpd_validate_moduledata(isp, cpd_file + ent->offset,
++					   ent->len);
++	if (ret)
++		dev_err(&isp->pdev->dev, "Invalid CPD moduledata\n");
++
++	return ret;
++}
+diff --git a/drivers/media/pci/intel/ipu6/ipu6-cpd.h b/drivers/media/pci/intel/ipu6/ipu6-cpd.h
+new file mode 100644
+index 000000000000..37465d507386
+--- /dev/null
++++ b/drivers/media/pci/intel/ipu6/ipu6-cpd.h
+@@ -0,0 +1,105 @@
++/* SPDX-License-Identifier: GPL-2.0-only */
++/* Copyright (C) 2015 - 2023 Intel Corporation */
++
++#ifndef IPU6_CPD_H
++#define IPU6_CPD_H
++
++struct ipu6_device;
++struct ipu6_bus_device;
++
++#define IPU6_CPD_SIZE_OF_FW_ARCH_VERSION	7
++#define IPU6_CPD_SIZE_OF_SYSTEM_VERSION		11
++#define IPU6_CPD_SIZE_OF_COMPONENT_NAME		12
++
++#define IPU6_CPD_METADATA_EXTN_TYPE_IUNIT	0x10
++
++#define IPU6_CPD_METADATA_IMAGE_TYPE_RESERVED		0
++#define IPU6_CPD_METADATA_IMAGE_TYPE_BOOTLOADER		1
++#define IPU6_CPD_METADATA_IMAGE_TYPE_MAIN_FIRMWARE	2
++
++#define IPU6_CPD_PKG_DIR_PSYS_SERVER_IDX	0
++#define IPU6_CPD_PKG_DIR_ISYS_SERVER_IDX	1
++
++#define IPU6_CPD_PKG_DIR_CLIENT_PG_TYPE		3
++
++#define IPU6_CPD_METADATA_HASH_KEY_SIZE		48
++#define IPU6SE_CPD_METADATA_HASH_KEY_SIZE	32
++
++struct ipu6_cpd_module_data_hdr {
++	u32 hdr_len;
++	u32 endian;
++	u32 fw_pkg_date;
++	u32 hive_sdk_date;
++	u32 compiler_date;
++	u32 target_platform_type;
++	u8 sys_ver[IPU6_CPD_SIZE_OF_SYSTEM_VERSION];
++	u8 fw_arch_ver[IPU6_CPD_SIZE_OF_FW_ARCH_VERSION];
++	u8 rsvd[2];
++} __packed;
++
++/*
++ * ipu6_cpd_hdr structure updated as the chksum and
++ * sub_partition_name is unused on host side
++ * CSE layout version 1.6 for IPU6SE (hdr_len = 0x10)
++ * CSE layout version 1.7 for IPU6 (hdr_len = 0x14)
++ */
++struct ipu6_cpd_hdr {
++	u32 hdr_mark;
++	u32 ent_cnt;
++	u8 hdr_ver;
++	u8 ent_ver;
++	u8 hdr_len;
++} __packed;
++
++struct ipu6_cpd_ent {
++	u8 name[IPU6_CPD_SIZE_OF_COMPONENT_NAME];
++	u32 offset;
++	u32 len;
++	u8 rsvd[4];
++} __packed;
++
++struct ipu6_cpd_metadata_cmpnt_hdr {
++	u32 id;
++	u32 size;
++	u32 ver;
++} __packed;
++
++struct ipu6_cpd_metadata_cmpnt {
++	struct ipu6_cpd_metadata_cmpnt_hdr hdr;
++	u8 sha2_hash[IPU6_CPD_METADATA_HASH_KEY_SIZE];
++	u32 entry_point;
++	u32 icache_base_offs;
++	u8 attrs[16];
++} __packed;
++
++struct ipu6se_cpd_metadata_cmpnt {
++	struct ipu6_cpd_metadata_cmpnt_hdr hdr;
++	u8 sha2_hash[IPU6SE_CPD_METADATA_HASH_KEY_SIZE];
++	u32 entry_point;
++	u32 icache_base_offs;
++	u8 attrs[16];
++} __packed;
++
++struct ipu6_cpd_metadata_extn {
++	u32 extn_type;
++	u32 len;
++	u32 img_type;
++	u8 rsvd[16];
++} __packed;
++
++struct ipu6_cpd_client_pkg_hdr {
++	u32 prog_list_offs;
++	u32 prog_list_size;
++	u32 prog_desc_offs;
++	u32 prog_desc_size;
++	u32 pg_manifest_offs;
++	u32 pg_manifest_size;
++	u32 prog_bin_offs;
++	u32 prog_bin_size;
++} __packed;
++
++int ipu6_cpd_create_pkg_dir(struct ipu6_bus_device *adev, const void *src);
++void ipu6_cpd_free_pkg_dir(struct ipu6_bus_device *adev);
++int ipu6_cpd_validate_cpd_file(struct ipu6_device *isp, const void *cpd_file,
++			       unsigned long cpd_file_size);
++#endif /* IPU6_CPD_H */
+-- 
+2.43.2
+
+
+From 86b4cd540a9cb10de7a521882495f5eba6cc919f Mon Sep 17 00:00:00 2001
+From: Bingbu Cao <bingbu.cao@intel.com>
+Date: Thu, 11 Jan 2024 14:55:19 +0800
+Subject: [PATCH 12/33] media: intel/ipu6: add IPU6 DMA mapping API and MMU
+ table
+
+he Intel IPU6 has an internal microcontroller (scalar processor, SP) which
+is used to execute the firmware. The SP can access IPU internal memory and
+map system DRAM to its an internal 32-bit virtual address space.
+
+This patch adds a driver for the IPU MMU and a DMA mapping implementation
+using the internal MMU. The system IOMMU may be used besides the IPU MMU.
+
+Signed-off-by: Bingbu Cao <bingbu.cao@intel.com>
+---
+ drivers/media/pci/intel/ipu6/ipu6-dma.c | 502 ++++++++++++++
+ drivers/media/pci/intel/ipu6/ipu6-dma.h |  19 +
+ drivers/media/pci/intel/ipu6/ipu6-mmu.c | 845 ++++++++++++++++++++++++
+ drivers/media/pci/intel/ipu6/ipu6-mmu.h |  73 ++
+ 4 files changed, 1439 insertions(+)
+ create mode 100644 drivers/media/pci/intel/ipu6/ipu6-dma.c
+ create mode 100644 drivers/media/pci/intel/ipu6/ipu6-dma.h
+ create mode 100644 drivers/media/pci/intel/ipu6/ipu6-mmu.c
+ create mode 100644 drivers/media/pci/intel/ipu6/ipu6-mmu.h
+
+diff --git a/drivers/media/pci/intel/ipu6/ipu6-dma.c b/drivers/media/pci/intel/ipu6/ipu6-dma.c
+new file mode 100644
+index 000000000000..3d77c6e5a45e
+--- /dev/null
++++ b/drivers/media/pci/intel/ipu6/ipu6-dma.c
+@@ -0,0 +1,502 @@
++// SPDX-License-Identifier: GPL-2.0-only
++/*
++ * Copyright (C) 2013 - 2023 Intel Corporation
++ */
++
++#include <linux/cacheflush.h>
++#include <linux/dma-mapping.h>
++#include <linux/iova.h>
++#include <linux/list.h>
++#include <linux/mm.h>
++#include <linux/vmalloc.h>
++#include <linux/scatterlist.h>
++#include <linux/slab.h>
++#include <linux/types.h>
++
++#include "ipu6.h"
++#include "ipu6-bus.h"
++#include "ipu6-dma.h"
++#include "ipu6-mmu.h"
++
++struct vm_info {
++	struct list_head list;
++	struct page **pages;
++	dma_addr_t ipu6_iova;
++	void *vaddr;
++	unsigned long size;
++};
++
++static struct vm_info *get_vm_info(struct ipu6_mmu *mmu, dma_addr_t iova)
++{
++	struct vm_info *info, *save;
++
++	list_for_each_entry_safe(info, save, &mmu->vma_list, list) {
++		if (iova >= info->ipu6_iova &&
++		    iova < (info->ipu6_iova + info->size))
++			return info;
++	}
++
++	return NULL;
++}
++
++static void __dma_clear_buffer(struct page *page, size_t size,
++			       unsigned long attrs)
++{
++	void *ptr;
++
++	if (!page)
++		return;
++	/*
++	 * Ensure that the allocated pages are zeroed, and that any data
++	 * lurking in the kernel direct-mapped region is invalidated.
++	 */
++	ptr = page_address(page);
++	memset(ptr, 0, size);
++	if ((attrs & DMA_ATTR_SKIP_CPU_SYNC) == 0)
++		clflush_cache_range(ptr, size);
++}
++
++static struct page **__dma_alloc_buffer(struct device *dev, size_t size,
++					gfp_t gfp, unsigned long attrs)
++{
++	int count = PHYS_PFN(size);
++	int array_size = count * sizeof(struct page *);
++	struct page **pages;
++	int i = 0;
++
++	pages = kvzalloc(array_size, GFP_KERNEL);
++	if (!pages)
++		return NULL;
++
++	gfp |= __GFP_NOWARN;
++
++	while (count) {
++		int j, order = __fls(count);
++
++		pages[i] = alloc_pages(gfp, order);
++		while (!pages[i] && order)
++			pages[i] = alloc_pages(gfp, --order);
++		if (!pages[i])
++			goto error;
++
++		if (order) {
++			split_page(pages[i], order);
++			j = 1 << order;
++			while (j--)
++				pages[i + j] = pages[i] + j;
++		}
++
++		__dma_clear_buffer(pages[i], PAGE_SIZE << order, attrs);
++		i += 1 << order;
++		count -= 1 << order;
++	}
++
++	return pages;
++error:
++	while (i--)
++		if (pages[i])
++			__free_pages(pages[i], 0);
++	kvfree(pages);
++	return NULL;
++}
++
++static void __dma_free_buffer(struct device *dev, struct page **pages,
++			      size_t size, unsigned long attrs)
++{
++	int count = PHYS_PFN(size);
++	unsigned int i;
++
++	for (i = 0; i < count && pages[i]; i++) {
++		__dma_clear_buffer(pages[i], PAGE_SIZE, attrs);
++		__free_pages(pages[i], 0);
++	}
++
++	kvfree(pages);
++}
++
++static void ipu6_dma_sync_single_for_cpu(struct device *dev,
++					 dma_addr_t dma_handle,
++					 size_t size,
++					 enum dma_data_direction dir)
++{
++	void *vaddr;
++	u32 offset;
++	struct vm_info *info;
++	struct ipu6_mmu *mmu = to_ipu6_bus_device(dev)->mmu;
++
++	info = get_vm_info(mmu, dma_handle);
++	if (WARN_ON(!info))
++		return;
++
++	offset = dma_handle - info->ipu6_iova;
++	if (WARN_ON(size > (info->size - offset)))
++		return;
++
++	vaddr = info->vaddr + offset;
++	clflush_cache_range(vaddr, size);
++}
++
++static void ipu6_dma_sync_sg_for_cpu(struct device *dev,
++				     struct scatterlist *sglist,
++				     int nents, enum dma_data_direction dir)
++{
++	struct scatterlist *sg;
++	int i;
++
++	for_each_sg(sglist, sg, nents, i)
++		clflush_cache_range(page_to_virt(sg_page(sg)), sg->length);
++}
++
++static void *ipu6_dma_alloc(struct device *dev, size_t size,
++			    dma_addr_t *dma_handle, gfp_t gfp,
++			    unsigned long attrs)
++{
++	struct ipu6_mmu *mmu = to_ipu6_bus_device(dev)->mmu;
++	struct pci_dev *pdev = to_ipu6_bus_device(dev)->isp->pdev;
++	dma_addr_t pci_dma_addr, ipu6_iova;
++	struct vm_info *info;
++	unsigned long count;
++	struct page **pages;
++	struct iova *iova;
++	unsigned int i;
++	int ret;
++
++	info = kzalloc(sizeof(*info), GFP_KERNEL);
++	if (!info)
++		return NULL;
++
++	size = PAGE_ALIGN(size);
++	count = PHYS_PFN(size);
++
++	iova = alloc_iova(&mmu->dmap->iovad, count,
++			  PHYS_PFN(dma_get_mask(dev)), 0);
++	if (!iova)
++		goto out_kfree;
++
++	pages = __dma_alloc_buffer(dev, size, gfp, attrs);
++	if (!pages)
++		goto out_free_iova;
++
++	dev_dbg(dev, "dma_alloc: size %zu iova low pfn %lu, high pfn %lu\n",
++		size, iova->pfn_lo, iova->pfn_hi);
++	for (i = 0; iova->pfn_lo + i <= iova->pfn_hi; i++) {
++		pci_dma_addr = dma_map_page_attrs(&pdev->dev, pages[i], 0,
++						  PAGE_SIZE, DMA_BIDIRECTIONAL,
++						  attrs);
++		dev_dbg(dev, "dma_alloc: mapped pci_dma_addr %pad\n",
++			&pci_dma_addr);
++		if (dma_mapping_error(&pdev->dev, pci_dma_addr)) {
++			dev_err(dev, "pci_dma_mapping for page[%d] failed", i);
++			goto out_unmap;
++		}
++
++		ret = ipu6_mmu_map(mmu->dmap->mmu_info,
++				   PFN_PHYS(iova->pfn_lo + i), pci_dma_addr,
++				   PAGE_SIZE);
++		if (ret) {
++			dev_err(dev, "ipu6_mmu_map for pci_dma[%d] %pad failed",
++				i, &pci_dma_addr);
++			dma_unmap_page_attrs(&pdev->dev, pci_dma_addr,
++					     PAGE_SIZE, DMA_BIDIRECTIONAL,
++					     attrs);
++			goto out_unmap;
++		}
++	}
++
++	info->vaddr = vmap(pages, count, VM_USERMAP, PAGE_KERNEL);
++	if (!info->vaddr)
++		goto out_unmap;
++
++	*dma_handle = PFN_PHYS(iova->pfn_lo);
++
++	info->pages = pages;
++	info->ipu6_iova = *dma_handle;
++	info->size = size;
++	list_add(&info->list, &mmu->vma_list);
++
++	return info->vaddr;
++
++out_unmap:
++	while (i--) {
++		ipu6_iova = PFN_PHYS(iova->pfn_lo + i);
++		pci_dma_addr = ipu6_mmu_iova_to_phys(mmu->dmap->mmu_info,
++						     ipu6_iova);
++		dma_unmap_page_attrs(&pdev->dev, pci_dma_addr, PAGE_SIZE,
++				     DMA_BIDIRECTIONAL, attrs);
++
++		ipu6_mmu_unmap(mmu->dmap->mmu_info, ipu6_iova, PAGE_SIZE);
++	}
++
++	__dma_free_buffer(dev, pages, size, attrs);
++
++out_free_iova:
++	__free_iova(&mmu->dmap->iovad, iova);
++out_kfree:
++	kfree(info);
++
++	return NULL;
++}
++
++static void ipu6_dma_free(struct device *dev, size_t size, void *vaddr,
++			  dma_addr_t dma_handle,
++			  unsigned long attrs)
++{
++	struct ipu6_mmu *mmu = to_ipu6_bus_device(dev)->mmu;
++	struct pci_dev *pdev = to_ipu6_bus_device(dev)->isp->pdev;
++	struct iova *iova = find_iova(&mmu->dmap->iovad, PHYS_PFN(dma_handle));
++	dma_addr_t pci_dma_addr, ipu6_iova;
++	struct vm_info *info;
++	struct page **pages;
++	unsigned int i;
++
++	if (WARN_ON(!iova))
++		return;
++
++	info = get_vm_info(mmu, dma_handle);
++	if (WARN_ON(!info))
++		return;
++
++	if (WARN_ON(!info->vaddr))
++		return;
++
++	if (WARN_ON(!info->pages))
++		return;
++
++	list_del(&info->list);
++
++	size = PAGE_ALIGN(size);
++
++	pages = info->pages;
++
++	vunmap(vaddr);
++
++	for (i = 0; i < PHYS_PFN(size); i++) {
++		ipu6_iova = PFN_PHYS(iova->pfn_lo + i);
++		pci_dma_addr = ipu6_mmu_iova_to_phys(mmu->dmap->mmu_info,
++						     ipu6_iova);
++		dma_unmap_page_attrs(&pdev->dev, pci_dma_addr, PAGE_SIZE,
++				     DMA_BIDIRECTIONAL, attrs);
++	}
++
++	ipu6_mmu_unmap(mmu->dmap->mmu_info, PFN_PHYS(iova->pfn_lo),
++		       PFN_PHYS(iova_size(iova)));
++
++	__dma_free_buffer(dev, pages, size, attrs);
++
++	mmu->tlb_invalidate(mmu);
++
++	__free_iova(&mmu->dmap->iovad, iova);
++
++	kfree(info);
++}
++
++static int ipu6_dma_mmap(struct device *dev, struct vm_area_struct *vma,
++			 void *addr, dma_addr_t iova, size_t size,
++			 unsigned long attrs)
++{
++	struct ipu6_mmu *mmu = to_ipu6_bus_device(dev)->mmu;
++	size_t count = PHYS_PFN(PAGE_ALIGN(size));
++	struct vm_info *info;
++	size_t i;
++	int ret;
++
++	info = get_vm_info(mmu, iova);
++	if (!info)
++		return -EFAULT;
++
++	if (!info->vaddr)
++		return -EFAULT;
++
++	if (vma->vm_start & ~PAGE_MASK)
++		return -EINVAL;
++
++	if (size > info->size)
++		return -EFAULT;
++
++	for (i = 0; i < count; i++) {
++		ret = vm_insert_page(vma, vma->vm_start + PFN_PHYS(i),
++				     info->pages[i]);
++		if (ret < 0)
++			return ret;
++	}
++
++	return 0;
++}
++
++static void ipu6_dma_unmap_sg(struct device *dev,
++			      struct scatterlist *sglist,
++			      int nents, enum dma_data_direction dir,
++			      unsigned long attrs)
++{
++	struct pci_dev *pdev = to_ipu6_bus_device(dev)->isp->pdev;
++	struct ipu6_mmu *mmu = to_ipu6_bus_device(dev)->mmu;
++	struct iova *iova = find_iova(&mmu->dmap->iovad,
++				      PHYS_PFN(sg_dma_address(sglist)));
++	int i, npages, count;
++	struct scatterlist *sg;
++	dma_addr_t pci_dma_addr;
++
++	if (!nents)
++		return;
++
++	if (WARN_ON(!iova))
++		return;
++
++	if ((attrs & DMA_ATTR_SKIP_CPU_SYNC) == 0)
++		ipu6_dma_sync_sg_for_cpu(dev, sglist, nents, DMA_BIDIRECTIONAL);
++
++	/* get the nents as orig_nents given by caller */
++	count = 0;
++	npages = iova_size(iova);
++	for_each_sg(sglist, sg, nents, i) {
++		if (sg_dma_len(sg) == 0 ||
++		    sg_dma_address(sg) == DMA_MAPPING_ERROR)
++			break;
++
++		npages -= PHYS_PFN(PAGE_ALIGN(sg_dma_len(sg)));
++		count++;
++		if (npages <= 0)
++			break;
++	}
++
++	/*
++	 * Before IPU6 mmu unmap, return the pci dma address back to sg
++	 * assume the nents is less than orig_nents as the least granule
++	 * is 1 SZ_4K page
++	 */
++	dev_dbg(dev, "trying to unmap concatenated %u ents\n", count);
++	for_each_sg(sglist, sg, count, i) {
++		dev_dbg(dev, "ipu unmap sg[%d] %pad\n", i, &sg_dma_address(sg));
++		pci_dma_addr = ipu6_mmu_iova_to_phys(mmu->dmap->mmu_info,
++						     sg_dma_address(sg));
++		dev_dbg(dev, "return pci_dma_addr %pad back to sg[%d]\n",
++			&pci_dma_addr, i);
++		sg_dma_address(sg) = pci_dma_addr;
++	}
++
++	dev_dbg(dev, "ipu6_mmu_unmap low pfn %lu high pfn %lu\n",
++		iova->pfn_lo, iova->pfn_hi);
++	ipu6_mmu_unmap(mmu->dmap->mmu_info, PFN_PHYS(iova->pfn_lo),
++		       PFN_PHYS(iova_size(iova)));
++
++	mmu->tlb_invalidate(mmu);
++
++	dma_unmap_sg_attrs(&pdev->dev, sglist, nents, dir, attrs);
++
++	__free_iova(&mmu->dmap->iovad, iova);
++}
++
++static int ipu6_dma_map_sg(struct device *dev, struct scatterlist *sglist,
++			   int nents, enum dma_data_direction dir,
++			   unsigned long attrs)
++{
++	struct ipu6_mmu *mmu = to_ipu6_bus_device(dev)->mmu;
++	struct pci_dev *pdev = to_ipu6_bus_device(dev)->isp->pdev;
++	struct scatterlist *sg;
++	struct iova *iova;
++	size_t npages = 0;
++	unsigned long iova_addr;
++	int i, count;
++
++	for_each_sg(sglist, sg, nents, i) {
++		if (sg->offset) {
++			dev_err(dev, "Unsupported non-zero sg[%d].offset %x\n",
++				i, sg->offset);
++			return -EFAULT;
++		}
++	}
++
++	dev_dbg(dev, "pci_dma_map_sg trying to map %d ents\n", nents);
++	count  = dma_map_sg_attrs(&pdev->dev, sglist, nents, dir, attrs);
++	if (count <= 0) {
++		dev_err(dev, "pci_dma_map_sg %d ents failed\n", nents);
++		return 0;
++	}
++
++	dev_dbg(dev, "pci_dma_map_sg %d ents mapped\n", count);
++
++	for_each_sg(sglist, sg, count, i)
++		npages += PHYS_PFN(PAGE_ALIGN(sg_dma_len(sg)));
++
++	iova = alloc_iova(&mmu->dmap->iovad, npages,
++			  PHYS_PFN(dma_get_mask(dev)), 0);
++	if (!iova)
++		return 0;
++
++	dev_dbg(dev, "dmamap: iova low pfn %lu, high pfn %lu\n", iova->pfn_lo,
++		iova->pfn_hi);
++
++	iova_addr = iova->pfn_lo;
++	for_each_sg(sglist, sg, count, i) {
++		int ret;
++
++		dev_dbg(dev, "mapping entry %d: iova 0x%llx phy %pad size %d\n",
++			i, PFN_PHYS(iova_addr), &sg_dma_address(sg),
++			sg_dma_len(sg));
++
++		ret = ipu6_mmu_map(mmu->dmap->mmu_info, PFN_PHYS(iova_addr),
++				   sg_dma_address(sg),
++				   PAGE_ALIGN(sg_dma_len(sg)));
++		if (ret)
++			goto out_fail;
++
++		sg_dma_address(sg) = PFN_PHYS(iova_addr);
++
++		iova_addr += PHYS_PFN(PAGE_ALIGN(sg_dma_len(sg)));
++	}
++
++	if ((attrs & DMA_ATTR_SKIP_CPU_SYNC) == 0)
++		ipu6_dma_sync_sg_for_cpu(dev, sglist, nents, DMA_BIDIRECTIONAL);
++
++	return count;
++
++out_fail:
++	ipu6_dma_unmap_sg(dev, sglist, i, dir, attrs);
++
++	return 0;
++}
++
++/*
++ * Create scatter-list for the already allocated DMA buffer
++ */
++static int ipu6_dma_get_sgtable(struct device *dev, struct sg_table *sgt,
++				void *cpu_addr, dma_addr_t handle, size_t size,
++				unsigned long attrs)
++{
++	struct ipu6_mmu *mmu = to_ipu6_bus_device(dev)->mmu;
++	struct vm_info *info;
++	int n_pages;
++	int ret = 0;
++
++	info = get_vm_info(mmu, handle);
++	if (!info)
++		return -EFAULT;
++
++	if (!info->vaddr)
++		return -EFAULT;
++
++	if (WARN_ON(!info->pages))
++		return -ENOMEM;
++
++	n_pages = PHYS_PFN(PAGE_ALIGN(size));
++
++	ret = sg_alloc_table_from_pages(sgt, info->pages, n_pages, 0, size,
++					GFP_KERNEL);
++	if (ret)
++		dev_warn(dev, "IPU6 get sgt table failed\n");
++
++	return ret;
++}
++
++const struct dma_map_ops ipu6_dma_ops = {
++	.alloc = ipu6_dma_alloc,
++	.free = ipu6_dma_free,
++	.mmap = ipu6_dma_mmap,
++	.map_sg = ipu6_dma_map_sg,
++	.unmap_sg = ipu6_dma_unmap_sg,
++	.sync_single_for_cpu = ipu6_dma_sync_single_for_cpu,
++	.sync_single_for_device = ipu6_dma_sync_single_for_cpu,
++	.sync_sg_for_cpu = ipu6_dma_sync_sg_for_cpu,
++	.sync_sg_for_device = ipu6_dma_sync_sg_for_cpu,
++	.get_sgtable = ipu6_dma_get_sgtable,
++};
+diff --git a/drivers/media/pci/intel/ipu6/ipu6-dma.h b/drivers/media/pci/intel/ipu6/ipu6-dma.h
+new file mode 100644
+index 000000000000..c75ad2462368
+--- /dev/null
++++ b/drivers/media/pci/intel/ipu6/ipu6-dma.h
+@@ -0,0 +1,19 @@
++/* SPDX-License-Identifier: GPL-2.0-only */
++/* Copyright (C) 2013 - 2023 Intel Corporation */
++
++#ifndef IPU6_DMA_H
++#define IPU6_DMA_H
++
++#include <linux/dma-map-ops.h>
++#include <linux/iova.h>
++
++struct ipu6_mmu_info;
++
++struct ipu6_dma_mapping {
++	struct ipu6_mmu_info *mmu_info;
++	struct iova_domain iovad;
++};
++
++extern const struct dma_map_ops ipu6_dma_ops;
++
++#endif /* IPU6_DMA_H */
+diff --git a/drivers/media/pci/intel/ipu6/ipu6-mmu.c b/drivers/media/pci/intel/ipu6/ipu6-mmu.c
+new file mode 100644
+index 000000000000..dc16d45187a8
+--- /dev/null
++++ b/drivers/media/pci/intel/ipu6/ipu6-mmu.c
+@@ -0,0 +1,845 @@
++// SPDX-License-Identifier: GPL-2.0-only
++/*
++ * Copyright (C) 2013 - 2023 Intel Corporation
++ */
++#include <asm/barrier.h>
++
++#include <linux/align.h>
++#include <linux/atomic.h>
++#include <linux/bitops.h>
++#include <linux/bits.h>
++#include <linux/bug.h>
++#include <linux/cacheflush.h>
++#include <linux/dma-mapping.h>
++#include <linux/err.h>
++#include <linux/gfp.h>
++#include <linux/io.h>
++#include <linux/iova.h>
++#include <linux/math.h>
++#include <linux/minmax.h>
++#include <linux/mm.h>
++#include <linux/pfn.h>
++#include <linux/slab.h>
++#include <linux/spinlock.h>
++#include <linux/types.h>
++
++#include "ipu6.h"
++#include "ipu6-dma.h"
++#include "ipu6-mmu.h"
++#include "ipu6-platform-regs.h"
++
++#define ISP_PAGE_SHIFT		12
++#define ISP_PAGE_SIZE		BIT(ISP_PAGE_SHIFT)
++#define ISP_PAGE_MASK		(~(ISP_PAGE_SIZE - 1))
++
++#define ISP_L1PT_SHIFT		22
++#define ISP_L1PT_MASK		(~((1U << ISP_L1PT_SHIFT) - 1))
++
++#define ISP_L2PT_SHIFT		12
++#define ISP_L2PT_MASK		(~(ISP_L1PT_MASK | (~(ISP_PAGE_MASK))))
++
++#define ISP_L1PT_PTES           1024
++#define ISP_L2PT_PTES           1024
++
++#define ISP_PADDR_SHIFT		12
++
++#define REG_TLB_INVALIDATE	0x0000
++
++#define REG_L1_PHYS		0x0004	/* 27-bit pfn */
++#define REG_INFO		0x0008
++
++#define TBL_PHYS_ADDR(a)	((phys_addr_t)(a) << ISP_PADDR_SHIFT)
++
++static void tlb_invalidate(struct ipu6_mmu *mmu)
++{
++	unsigned long flags;
++	unsigned int i;
++
++	spin_lock_irqsave(&mmu->ready_lock, flags);
++	if (!mmu->ready) {
++		spin_unlock_irqrestore(&mmu->ready_lock, flags);
++		return;
++	}
++
++	for (i = 0; i < mmu->nr_mmus; i++) {
++		/*
++		 * To avoid the HW bug induced dead lock in some of the IPU6
++		 * MMUs on successive invalidate calls, we need to first do a
++		 * read to the page table base before writing the invalidate
++		 * register. MMUs which need to implement this WA, will have
++		 * the insert_read_before_invalidate flags set as true.
++		 * Disregard the return value of the read.
++		 */
++		if (mmu->mmu_hw[i].insert_read_before_invalidate)
++			readl(mmu->mmu_hw[i].base + REG_L1_PHYS);
++
++		writel(0xffffffff, mmu->mmu_hw[i].base +
++		       REG_TLB_INVALIDATE);
++		/*
++		 * The TLB invalidation is a "single cycle" (IOMMU clock cycles)
++		 * When the actual MMIO write reaches the IPU6 TLB Invalidate
++		 * register, wmb() will force the TLB invalidate out if the CPU
++		 * attempts to update the IOMMU page table (or sooner).
++		 */
++		wmb();
++	}
++	spin_unlock_irqrestore(&mmu->ready_lock, flags);
++}
++
++#ifdef DEBUG
++static void page_table_dump(struct ipu6_mmu_info *mmu_info)
++{
++	u32 l1_idx;
++
++	dev_dbg(mmu_info->dev, "begin IOMMU page table dump\n");
++
++	for (l1_idx = 0; l1_idx < ISP_L1PT_PTES; l1_idx++) {
++		u32 l2_idx;
++		u32 iova = (phys_addr_t)l1_idx << ISP_L1PT_SHIFT;
++
++		if (mmu_info->l1_pt[l1_idx] == mmu_info->dummy_l2_pteval)
++			continue;
++		dev_dbg(mmu_info->dev,
++			"l1 entry %u; iovas 0x%8.8x-0x%8.8x, at %pa\n",
++			l1_idx, iova, iova + ISP_PAGE_SIZE,
++			TBL_PHYS_ADDR(mmu_info->l1_pt[l1_idx]));
++
++		for (l2_idx = 0; l2_idx < ISP_L2PT_PTES; l2_idx++) {
++			u32 *l2_pt = mmu_info->l2_pts[l1_idx];
++			u32 iova2 = iova + (l2_idx << ISP_L2PT_SHIFT);
++
++			if (l2_pt[l2_idx] == mmu_info->dummy_page_pteval)
++				continue;
++
++			dev_dbg(mmu_info->dev,
++				"\tl2 entry %u; iova 0x%8.8x, phys %pa\n",
++				l2_idx, iova2,
++				TBL_PHYS_ADDR(l2_pt[l2_idx]));
++		}
++	}
++
++	dev_dbg(mmu_info->dev, "end IOMMU page table dump\n");
++}
++#endif /* DEBUG */
++
++static dma_addr_t map_single(struct ipu6_mmu_info *mmu_info, void *ptr)
++{
++	dma_addr_t dma;
++
++	dma = dma_map_single(mmu_info->dev, ptr, PAGE_SIZE, DMA_BIDIRECTIONAL);
++	if (dma_mapping_error(mmu_info->dev, dma))
++		return 0;
++
++	return dma;
++}
++
++static int get_dummy_page(struct ipu6_mmu_info *mmu_info)
++{
++	void *pt = (void *)get_zeroed_page(GFP_ATOMIC | GFP_DMA32);
++	dma_addr_t dma;
++
++	if (!pt)
++		return -ENOMEM;
++
++	dev_dbg(mmu_info->dev, "dummy_page: get_zeroed_page() == %p\n", pt);
++
++	dma = map_single(mmu_info, pt);
++	if (!dma) {
++		dev_err(mmu_info->dev, "Failed to map dummy page\n");
++		goto err_free_page;
++	}
++
++	mmu_info->dummy_page = pt;
++	mmu_info->dummy_page_pteval = dma >> ISP_PAGE_SHIFT;
++
++	return 0;
++
++err_free_page:
++	free_page((unsigned long)pt);
++	return -ENOMEM;
++}
++
++static void free_dummy_page(struct ipu6_mmu_info *mmu_info)
++{
++	dma_unmap_single(mmu_info->dev,
++			 TBL_PHYS_ADDR(mmu_info->dummy_page_pteval),
++			 PAGE_SIZE, DMA_BIDIRECTIONAL);
++	free_page((unsigned long)mmu_info->dummy_page);
++}
++
++static int alloc_dummy_l2_pt(struct ipu6_mmu_info *mmu_info)
++{
++	u32 *pt = (u32 *)get_zeroed_page(GFP_ATOMIC | GFP_DMA32);
++	dma_addr_t dma;
++	unsigned int i;
++
++	if (!pt)
++		return -ENOMEM;
++
++	dev_dbg(mmu_info->dev, "dummy_l2: get_zeroed_page() = %p\n", pt);
++
++	dma = map_single(mmu_info, pt);
++	if (!dma) {
++		dev_err(mmu_info->dev, "Failed to map l2pt page\n");
++		goto err_free_page;
++	}
++
++	for (i = 0; i < ISP_L2PT_PTES; i++)
++		pt[i] = mmu_info->dummy_page_pteval;
++
++	mmu_info->dummy_l2_pt = pt;
++	mmu_info->dummy_l2_pteval = dma >> ISP_PAGE_SHIFT;
++
++	return 0;
++
++err_free_page:
++	free_page((unsigned long)pt);
++	return -ENOMEM;
++}
++
++static void free_dummy_l2_pt(struct ipu6_mmu_info *mmu_info)
++{
++	dma_unmap_single(mmu_info->dev,
++			 TBL_PHYS_ADDR(mmu_info->dummy_l2_pteval),
++			 PAGE_SIZE, DMA_BIDIRECTIONAL);
++	free_page((unsigned long)mmu_info->dummy_l2_pt);
++}
++
++static u32 *alloc_l1_pt(struct ipu6_mmu_info *mmu_info)
++{
++	u32 *pt = (u32 *)get_zeroed_page(GFP_ATOMIC | GFP_DMA32);
++	dma_addr_t dma;
++	unsigned int i;
++
++	if (!pt)
++		return NULL;
++
++	dev_dbg(mmu_info->dev, "alloc_l1: get_zeroed_page() = %p\n", pt);
++
++	for (i = 0; i < ISP_L1PT_PTES; i++)
++		pt[i] = mmu_info->dummy_l2_pteval;
++
++	dma = map_single(mmu_info, pt);
++	if (!dma) {
++		dev_err(mmu_info->dev, "Failed to map l1pt page\n");
++		goto err_free_page;
++	}
++
++	mmu_info->l1_pt_dma = dma >> ISP_PADDR_SHIFT;
++	dev_dbg(mmu_info->dev, "l1 pt %p mapped at %llx\n", pt, dma);
++
++	return pt;
++
++err_free_page:
++	free_page((unsigned long)pt);
++	return NULL;
++}
++
++static u32 *alloc_l2_pt(struct ipu6_mmu_info *mmu_info)
++{
++	u32 *pt = (u32 *)get_zeroed_page(GFP_ATOMIC | GFP_DMA32);
++	unsigned int i;
++
++	if (!pt)
++		return NULL;
++
++	dev_dbg(mmu_info->dev, "alloc_l2: get_zeroed_page() = %p\n", pt);
++
++	for (i = 0; i < ISP_L1PT_PTES; i++)
++		pt[i] = mmu_info->dummy_page_pteval;
++
++	return pt;
++}
++
++static int l2_map(struct ipu6_mmu_info *mmu_info, unsigned long iova,
++		  phys_addr_t paddr, size_t size)
++{
++	u32 l1_idx = iova >> ISP_L1PT_SHIFT;
++	u32 iova_start = iova;
++	u32 *l2_pt, *l2_virt;
++	unsigned int l2_idx;
++	unsigned long flags;
++	dma_addr_t dma;
++	u32 l1_entry;
++
++	dev_dbg(mmu_info->dev,
++		"mapping l2 page table for l1 index %u (iova %8.8x)\n",
++		l1_idx, (u32)iova);
++
++	spin_lock_irqsave(&mmu_info->lock, flags);
++	l1_entry = mmu_info->l1_pt[l1_idx];
++	if (l1_entry == mmu_info->dummy_l2_pteval) {
++		l2_virt = mmu_info->l2_pts[l1_idx];
++		if (likely(!l2_virt)) {
++			l2_virt = alloc_l2_pt(mmu_info);
++			if (!l2_virt) {
++				spin_unlock_irqrestore(&mmu_info->lock, flags);
++				return -ENOMEM;
++			}
++		}
++
++		dma = map_single(mmu_info, l2_virt);
++		if (!dma) {
++			dev_err(mmu_info->dev, "Failed to map l2pt page\n");
++			free_page((unsigned long)l2_virt);
++			spin_unlock_irqrestore(&mmu_info->lock, flags);
++			return -EINVAL;
++		}
++
++		l1_entry = dma >> ISP_PADDR_SHIFT;
++
++		dev_dbg(mmu_info->dev, "page for l1_idx %u %p allocated\n",
++			l1_idx, l2_virt);
++		mmu_info->l1_pt[l1_idx] = l1_entry;
++		mmu_info->l2_pts[l1_idx] = l2_virt;
++		clflush_cache_range((void *)&mmu_info->l1_pt[l1_idx],
++				    sizeof(mmu_info->l1_pt[l1_idx]));
++	}
++
++	l2_pt = mmu_info->l2_pts[l1_idx];
++
++	dev_dbg(mmu_info->dev, "l2_pt at %p with dma 0x%x\n", l2_pt, l1_entry);
++
++	paddr = ALIGN(paddr, ISP_PAGE_SIZE);
++
++	l2_idx = (iova_start & ISP_L2PT_MASK) >> ISP_L2PT_SHIFT;
++
++	dev_dbg(mmu_info->dev, "l2_idx %u, phys 0x%8.8x\n", l2_idx,
++		l2_pt[l2_idx]);
++	if (l2_pt[l2_idx] != mmu_info->dummy_page_pteval) {
++		spin_unlock_irqrestore(&mmu_info->lock, flags);
++		return -EINVAL;
++	}
++
++	l2_pt[l2_idx] = paddr >> ISP_PADDR_SHIFT;
++
++	clflush_cache_range((void *)&l2_pt[l2_idx], sizeof(l2_pt[l2_idx]));
++	spin_unlock_irqrestore(&mmu_info->lock, flags);
++
++	dev_dbg(mmu_info->dev, "l2 index %u mapped as 0x%8.8x\n", l2_idx,
++		l2_pt[l2_idx]);
++
++	return 0;
++}
++
++static int __ipu6_mmu_map(struct ipu6_mmu_info *mmu_info, unsigned long iova,
++			  phys_addr_t paddr, size_t size)
++{
++	u32 iova_start = round_down(iova, ISP_PAGE_SIZE);
++	u32 iova_end = ALIGN(iova + size, ISP_PAGE_SIZE);
++
++	dev_dbg(mmu_info->dev,
++		"mapping iova 0x%8.8x--0x%8.8x, size %zu at paddr 0x%10.10llx\n",
++		iova_start, iova_end, size, paddr);
++
++	return l2_map(mmu_info, iova_start, paddr, size);
++}
++
++static size_t l2_unmap(struct ipu6_mmu_info *mmu_info, unsigned long iova,
++		       phys_addr_t dummy, size_t size)
++{
++	u32 l1_idx = iova >> ISP_L1PT_SHIFT;
++	u32 iova_start = iova;
++	unsigned int l2_idx;
++	size_t unmapped = 0;
++	unsigned long flags;
++	u32 *l2_pt;
++
++	dev_dbg(mmu_info->dev, "unmapping l2 page table for l1 index %u (iova 0x%8.8lx)\n",
++		l1_idx, iova);
++
++	spin_lock_irqsave(&mmu_info->lock, flags);
++	if (mmu_info->l1_pt[l1_idx] == mmu_info->dummy_l2_pteval) {
++		spin_unlock_irqrestore(&mmu_info->lock, flags);
++		dev_err(mmu_info->dev,
++			"unmap iova 0x%8.8lx l1 idx %u which was not mapped\n",
++			iova, l1_idx);
++		return 0;
++	}
++
++	for (l2_idx = (iova_start & ISP_L2PT_MASK) >> ISP_L2PT_SHIFT;
++	     (iova_start & ISP_L1PT_MASK) + (l2_idx << ISP_PAGE_SHIFT)
++		     < iova_start + size && l2_idx < ISP_L2PT_PTES; l2_idx++) {
++		l2_pt = mmu_info->l2_pts[l1_idx];
++		dev_dbg(mmu_info->dev,
++			"unmap l2 index %u with pteval 0x%10.10llx\n",
++			l2_idx, TBL_PHYS_ADDR(l2_pt[l2_idx]));
++		l2_pt[l2_idx] = mmu_info->dummy_page_pteval;
++
++		clflush_cache_range((void *)&l2_pt[l2_idx],
++				    sizeof(l2_pt[l2_idx]));
++		unmapped++;
++	}
++	spin_unlock_irqrestore(&mmu_info->lock, flags);
++
++	return unmapped << ISP_PAGE_SHIFT;
++}
++
++static size_t __ipu6_mmu_unmap(struct ipu6_mmu_info *mmu_info,
++			       unsigned long iova, size_t size)
++{
++	return l2_unmap(mmu_info, iova, 0, size);
++}
++
++static int allocate_trash_buffer(struct ipu6_mmu *mmu)
++{
++	unsigned int n_pages = PHYS_PFN(PAGE_ALIGN(IPU6_MMUV2_TRASH_RANGE));
++	struct iova *iova;
++	unsigned int i;
++	dma_addr_t dma;
++	unsigned long iova_addr;
++	int ret;
++
++	/* Allocate 8MB in iova range */
++	iova = alloc_iova(&mmu->dmap->iovad, n_pages,
++			  PHYS_PFN(mmu->dmap->mmu_info->aperture_end), 0);
++	if (!iova) {
++		dev_err(mmu->dev, "cannot allocate iova range for trash\n");
++		return -ENOMEM;
++	}
++
++	dma = dma_map_page(mmu->dmap->mmu_info->dev, mmu->trash_page, 0,
++			   PAGE_SIZE, DMA_BIDIRECTIONAL);
++	if (dma_mapping_error(mmu->dmap->mmu_info->dev, dma)) {
++		dev_err(mmu->dmap->mmu_info->dev, "Failed to map trash page\n");
++		ret = -ENOMEM;
++		goto out_free_iova;
++	}
++
++	mmu->pci_trash_page = dma;
++
++	/*
++	 * Map the 8MB iova address range to the same physical trash page
++	 * mmu->trash_page which is already reserved at the probe
++	 */
++	iova_addr = iova->pfn_lo;
++	for (i = 0; i < n_pages; i++) {
++		ret = ipu6_mmu_map(mmu->dmap->mmu_info, PFN_PHYS(iova_addr),
++				   mmu->pci_trash_page, PAGE_SIZE);
++		if (ret) {
++			dev_err(mmu->dev,
++				"mapping trash buffer range failed\n");
++			goto out_unmap;
++		}
++
++		iova_addr++;
++	}
++
++	mmu->iova_trash_page = PFN_PHYS(iova->pfn_lo);
++	dev_dbg(mmu->dev, "iova trash buffer for MMUID: %d is %u\n",
++		mmu->mmid, (unsigned int)mmu->iova_trash_page);
++	return 0;
++
++out_unmap:
++	ipu6_mmu_unmap(mmu->dmap->mmu_info, PFN_PHYS(iova->pfn_lo),
++		       PFN_PHYS(iova_size(iova)));
++	dma_unmap_page(mmu->dmap->mmu_info->dev, mmu->pci_trash_page,
++		       PAGE_SIZE, DMA_BIDIRECTIONAL);
++out_free_iova:
++	__free_iova(&mmu->dmap->iovad, iova);
++	return ret;
++}
++
++int ipu6_mmu_hw_init(struct ipu6_mmu *mmu)
++{
++	struct ipu6_mmu_info *mmu_info;
++	unsigned long flags;
++	unsigned int i;
++
++	mmu_info = mmu->dmap->mmu_info;
++
++	/* Initialise the each MMU HW block */
++	for (i = 0; i < mmu->nr_mmus; i++) {
++		struct ipu6_mmu_hw *mmu_hw = &mmu->mmu_hw[i];
++		unsigned int j;
++		u16 block_addr;
++
++		/* Write page table address per MMU */
++		writel((phys_addr_t)mmu_info->l1_pt_dma,
++		       mmu->mmu_hw[i].base + REG_L1_PHYS);
++
++		/* Set info bits per MMU */
++		writel(mmu->mmu_hw[i].info_bits,
++		       mmu->mmu_hw[i].base + REG_INFO);
++
++		/* Configure MMU TLB stream configuration for L1 */
++		for (j = 0, block_addr = 0; j < mmu_hw->nr_l1streams;
++		     block_addr += mmu->mmu_hw[i].l1_block_sz[j], j++) {
++			if (block_addr > IPU6_MAX_LI_BLOCK_ADDR) {
++				dev_err(mmu->dev, "invalid L1 configuration\n");
++				return -EINVAL;
++			}
++
++			/* Write block start address for each streams */
++			writel(block_addr, mmu_hw->base +
++			       mmu_hw->l1_stream_id_reg_offset + 4 * j);
++		}
++
++		/* Configure MMU TLB stream configuration for L2 */
++		for (j = 0, block_addr = 0; j < mmu_hw->nr_l2streams;
++		     block_addr += mmu->mmu_hw[i].l2_block_sz[j], j++) {
++			if (block_addr > IPU6_MAX_L2_BLOCK_ADDR) {
++				dev_err(mmu->dev, "invalid L2 configuration\n");
++				return -EINVAL;
++			}
++
++			writel(block_addr, mmu_hw->base +
++			       mmu_hw->l2_stream_id_reg_offset + 4 * j);
++		}
++	}
++
++	if (!mmu->trash_page) {
++		int ret;
++
++		mmu->trash_page = alloc_page(GFP_KERNEL);
++		if (!mmu->trash_page) {
++			dev_err(mmu->dev, "insufficient memory for trash buffer\n");
++			return -ENOMEM;
++		}
++
++		ret = allocate_trash_buffer(mmu);
++		if (ret) {
++			__free_page(mmu->trash_page);
++			mmu->trash_page = NULL;
++			dev_err(mmu->dev, "trash buffer allocation failed\n");
++			return ret;
++		}
++	}
++
++	spin_lock_irqsave(&mmu->ready_lock, flags);
++	mmu->ready = true;
++	spin_unlock_irqrestore(&mmu->ready_lock, flags);
++
++	return 0;
++}
++EXPORT_SYMBOL_NS_GPL(ipu6_mmu_hw_init, INTEL_IPU6);
++
++static struct ipu6_mmu_info *ipu6_mmu_alloc(struct ipu6_device *isp)
++{
++	struct ipu6_mmu_info *mmu_info;
++	int ret;
++
++	mmu_info = kzalloc(sizeof(*mmu_info), GFP_KERNEL);
++	if (!mmu_info)
++		return NULL;
++
++	mmu_info->aperture_start = 0;
++	mmu_info->aperture_end = DMA_BIT_MASK(isp->secure_mode ?
++					      IPU6_MMU_ADDR_BITS :
++					      IPU6_MMU_ADDR_BITS_NON_SECURE);
++	mmu_info->pgsize_bitmap = SZ_4K;
++	mmu_info->dev = &isp->pdev->dev;
++
++	ret = get_dummy_page(mmu_info);
++	if (ret)
++		goto err_free_info;
++
++	ret = alloc_dummy_l2_pt(mmu_info);
++	if (ret)
++		goto err_free_dummy_page;
++
++	mmu_info->l2_pts = vzalloc(ISP_L2PT_PTES * sizeof(*mmu_info->l2_pts));
++	if (!mmu_info->l2_pts)
++		goto err_free_dummy_l2_pt;
++
++	/*
++	 * We always map the L1 page table (a single page as well as
++	 * the L2 page tables).
++	 */
++	mmu_info->l1_pt = alloc_l1_pt(mmu_info);
++	if (!mmu_info->l1_pt)
++		goto err_free_l2_pts;
++
++	spin_lock_init(&mmu_info->lock);
++
++	dev_dbg(mmu_info->dev, "domain initialised\n");
++
++	return mmu_info;
++
++err_free_l2_pts:
++	vfree(mmu_info->l2_pts);
++err_free_dummy_l2_pt:
++	free_dummy_l2_pt(mmu_info);
++err_free_dummy_page:
++	free_dummy_page(mmu_info);
++err_free_info:
++	kfree(mmu_info);
++
++	return NULL;
++}
++
++void ipu6_mmu_hw_cleanup(struct ipu6_mmu *mmu)
++{
++	unsigned long flags;
++
++	spin_lock_irqsave(&mmu->ready_lock, flags);
++	mmu->ready = false;
++	spin_unlock_irqrestore(&mmu->ready_lock, flags);
++}
++EXPORT_SYMBOL_NS_GPL(ipu6_mmu_hw_cleanup, INTEL_IPU6);
++
++static struct ipu6_dma_mapping *alloc_dma_mapping(struct ipu6_device *isp)
++{
++	struct ipu6_dma_mapping *dmap;
++
++	dmap = kzalloc(sizeof(*dmap), GFP_KERNEL);
++	if (!dmap)
++		return NULL;
++
++	dmap->mmu_info = ipu6_mmu_alloc(isp);
++	if (!dmap->mmu_info) {
++		kfree(dmap);
++		return NULL;
++	}
++
++	init_iova_domain(&dmap->iovad, SZ_4K, 1);
++	dmap->mmu_info->dmap = dmap;
++
++	dev_dbg(&isp->pdev->dev, "alloc mapping\n");
++
++	iova_cache_get();
++
++	return dmap;
++}
++
++phys_addr_t ipu6_mmu_iova_to_phys(struct ipu6_mmu_info *mmu_info,
++				  dma_addr_t iova)
++{
++	phys_addr_t phy_addr;
++	unsigned long flags;
++	u32 *l2_pt;
++
++	spin_lock_irqsave(&mmu_info->lock, flags);
++	l2_pt = mmu_info->l2_pts[iova >> ISP_L1PT_SHIFT];
++	phy_addr = (phys_addr_t)l2_pt[(iova & ISP_L2PT_MASK) >> ISP_L2PT_SHIFT];
++	phy_addr <<= ISP_PAGE_SHIFT;
++	spin_unlock_irqrestore(&mmu_info->lock, flags);
++
++	return phy_addr;
++}
++
++static size_t ipu6_mmu_pgsize(unsigned long pgsize_bitmap,
++			      unsigned long addr_merge, size_t size)
++{
++	unsigned int pgsize_idx;
++	size_t pgsize;
++
++	/* Max page size that still fits into 'size' */
++	pgsize_idx = __fls(size);
++
++	if (likely(addr_merge)) {
++		/* Max page size allowed by address */
++		unsigned int align_pgsize_idx = __ffs(addr_merge);
++
++		pgsize_idx = min(pgsize_idx, align_pgsize_idx);
++	}
++
++	pgsize = (1UL << (pgsize_idx + 1)) - 1;
++	pgsize &= pgsize_bitmap;
++
++	WARN_ON(!pgsize);
++
++	/* pick the biggest page */
++	pgsize_idx = __fls(pgsize);
++	pgsize = 1UL << pgsize_idx;
++
++	return pgsize;
++}
++
++size_t ipu6_mmu_unmap(struct ipu6_mmu_info *mmu_info, unsigned long iova,
++		      size_t size)
++{
++	size_t unmapped_page, unmapped = 0;
++	unsigned int min_pagesz;
++
++	/* find out the minimum page size supported */
++	min_pagesz = 1 << __ffs(mmu_info->pgsize_bitmap);
++
++	/*
++	 * The virtual address and the size of the mapping must be
++	 * aligned (at least) to the size of the smallest page supported
++	 * by the hardware
++	 */
++	if (!IS_ALIGNED(iova | size, min_pagesz)) {
++		dev_err(NULL, "unaligned: iova 0x%lx size 0x%zx min_pagesz 0x%x\n",
++			iova, size, min_pagesz);
++		return -EINVAL;
++	}
++
++	/*
++	 * Keep iterating until we either unmap 'size' bytes (or more)
++	 * or we hit an area that isn't mapped.
++	 */
++	while (unmapped < size) {
++		size_t pgsize = ipu6_mmu_pgsize(mmu_info->pgsize_bitmap,
++						iova, size - unmapped);
++
++		unmapped_page = __ipu6_mmu_unmap(mmu_info, iova, pgsize);
++		if (!unmapped_page)
++			break;
++
++		dev_dbg(mmu_info->dev, "unmapped: iova 0x%lx size 0x%zx\n",
++			iova, unmapped_page);
++
++		iova += unmapped_page;
++		unmapped += unmapped_page;
++	}
++
++	return unmapped;
++}
++
++int ipu6_mmu_map(struct ipu6_mmu_info *mmu_info, unsigned long iova,
++		 phys_addr_t paddr, size_t size)
++{
++	unsigned long orig_iova = iova;
++	unsigned int min_pagesz;
++	size_t orig_size = size;
++	int ret = 0;
++
++	if (mmu_info->pgsize_bitmap == 0UL)
++		return -ENODEV;
++
++	/* find out the minimum page size supported */
++	min_pagesz = 1 << __ffs(mmu_info->pgsize_bitmap);
++
++	/*
++	 * both the virtual address and the physical one, as well as
++	 * the size of the mapping, must be aligned (at least) to the
++	 * size of the smallest page supported by the hardware
++	 */
++	if (!IS_ALIGNED(iova | paddr | size, min_pagesz)) {
++		dev_err(mmu_info->dev,
++			"unaligned: iova %lx pa %pa size %zx min_pagesz %x\n",
++			iova, &paddr, size, min_pagesz);
++		return -EINVAL;
++	}
++
++	dev_dbg(mmu_info->dev, "map: iova 0x%lx pa %pa size 0x%zx\n",
++		iova, &paddr, size);
++
++	while (size) {
++		size_t pgsize = ipu6_mmu_pgsize(mmu_info->pgsize_bitmap,
++						iova | paddr, size);
++
++		dev_dbg(mmu_info->dev,
++			"mapping: iova 0x%lx pa %pa pgsize 0x%zx\n",
++			iova, &paddr, pgsize);
++
++		ret = __ipu6_mmu_map(mmu_info, iova, paddr, pgsize);
++		if (ret)
++			break;
++
++		iova += pgsize;
++		paddr += pgsize;
++		size -= pgsize;
++	}
++
++	/* unroll mapping in case something went wrong */
++	if (ret)
++		ipu6_mmu_unmap(mmu_info, orig_iova, orig_size - size);
++
++	return ret;
++}
++
++static void ipu6_mmu_destroy(struct ipu6_mmu *mmu)
++{
++	struct ipu6_dma_mapping *dmap = mmu->dmap;
++	struct ipu6_mmu_info *mmu_info = dmap->mmu_info;
++	struct iova *iova;
++	u32 l1_idx;
++
++	if (mmu->iova_trash_page) {
++		iova = find_iova(&dmap->iovad, PHYS_PFN(mmu->iova_trash_page));
++		if (iova) {
++			/* unmap and free the trash buffer iova */
++			ipu6_mmu_unmap(mmu_info, PFN_PHYS(iova->pfn_lo),
++				       PFN_PHYS(iova_size(iova)));
++			__free_iova(&dmap->iovad, iova);
++		} else {
++			dev_err(mmu->dev, "trash buffer iova not found.\n");
++		}
++
++		mmu->iova_trash_page = 0;
++		dma_unmap_page(mmu_info->dev, mmu->pci_trash_page,
++			       PAGE_SIZE, DMA_BIDIRECTIONAL);
++		mmu->pci_trash_page = 0;
++		__free_page(mmu->trash_page);
++	}
++
++	for (l1_idx = 0; l1_idx < ISP_L1PT_PTES; l1_idx++) {
++		if (mmu_info->l1_pt[l1_idx] != mmu_info->dummy_l2_pteval) {
++			dma_unmap_single(mmu_info->dev,
++					 TBL_PHYS_ADDR(mmu_info->l1_pt[l1_idx]),
++					 PAGE_SIZE, DMA_BIDIRECTIONAL);
++			free_page((unsigned long)mmu_info->l2_pts[l1_idx]);
++		}
++	}
++
++	vfree(mmu_info->l2_pts);
++	free_dummy_page(mmu_info);
++	dma_unmap_single(mmu_info->dev, TBL_PHYS_ADDR(mmu_info->l1_pt_dma),
++			 PAGE_SIZE, DMA_BIDIRECTIONAL);
++	free_page((unsigned long)mmu_info->dummy_l2_pt);
++	free_page((unsigned long)mmu_info->l1_pt);
++	kfree(mmu_info);
++}
++
++struct ipu6_mmu *ipu6_mmu_init(struct device *dev,
++			       void __iomem *base, int mmid,
++			       const struct ipu6_hw_variants *hw)
++{
++	struct ipu6_device *isp = pci_get_drvdata(to_pci_dev(dev));
++	struct ipu6_mmu_pdata *pdata;
++	struct ipu6_mmu *mmu;
++	unsigned int i;
++
++	if (hw->nr_mmus > IPU6_MMU_MAX_DEVICES)
++		return ERR_PTR(-EINVAL);
++
++	pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL);
++	if (!pdata)
++		return ERR_PTR(-ENOMEM);
++
++	for (i = 0; i < hw->nr_mmus; i++) {
++		struct ipu6_mmu_hw *pdata_mmu = &pdata->mmu_hw[i];
++		const struct ipu6_mmu_hw *src_mmu = &hw->mmu_hw[i];
++
++		if (src_mmu->nr_l1streams > IPU6_MMU_MAX_TLB_L1_STREAMS ||
++		    src_mmu->nr_l2streams > IPU6_MMU_MAX_TLB_L2_STREAMS)
++			return ERR_PTR(-EINVAL);
++
++		*pdata_mmu = *src_mmu;
++		pdata_mmu->base = base + src_mmu->offset;
++	}
++
++	mmu = devm_kzalloc(dev, sizeof(*mmu), GFP_KERNEL);
++	if (!mmu)
++		return ERR_PTR(-ENOMEM);
++
++	mmu->mmid = mmid;
++	mmu->mmu_hw = pdata->mmu_hw;
++	mmu->nr_mmus = hw->nr_mmus;
++	mmu->tlb_invalidate = tlb_invalidate;
++	mmu->ready = false;
++	INIT_LIST_HEAD(&mmu->vma_list);
++	spin_lock_init(&mmu->ready_lock);
++
++	mmu->dmap = alloc_dma_mapping(isp);
++	if (!mmu->dmap) {
++		dev_err(dev, "can't alloc dma mapping\n");
++		return ERR_PTR(-ENOMEM);
++	}
++
++	return mmu;
++}
++
++void ipu6_mmu_cleanup(struct ipu6_mmu *mmu)
++{
++	struct ipu6_dma_mapping *dmap = mmu->dmap;
++
++	ipu6_mmu_destroy(mmu);
++	mmu->dmap = NULL;
++	iova_cache_put();
++	put_iova_domain(&dmap->iovad);
++	kfree(dmap);
++}
+diff --git a/drivers/media/pci/intel/ipu6/ipu6-mmu.h b/drivers/media/pci/intel/ipu6/ipu6-mmu.h
+new file mode 100644
+index 000000000000..95df7931a2e5
+--- /dev/null
++++ b/drivers/media/pci/intel/ipu6/ipu6-mmu.h
+@@ -0,0 +1,73 @@
++/* SPDX-License-Identifier: GPL-2.0-only */
++/* Copyright (C) 2013 - 2023 Intel Corporation */
++
++#ifndef IPU6_MMU_H
++#define IPU6_MMU_H
++
++#define ISYS_MMID 1
++#define PSYS_MMID 0
++
++#include <linux/list.h>
++#include <linux/spinlock_types.h>
++#include <linux/types.h>
++
++struct device;
++struct page;
++struct ipu6_hw_variants;
++
++struct ipu6_mmu_info {
++	struct device *dev;
++
++	u32 *l1_pt;
++	u32 l1_pt_dma;
++	u32 **l2_pts;
++
++	u32 *dummy_l2_pt;
++	u32 dummy_l2_pteval;
++	void *dummy_page;
++	u32 dummy_page_pteval;
++
++	dma_addr_t aperture_start;
++	dma_addr_t aperture_end;
++	unsigned long pgsize_bitmap;
++
++	spinlock_t lock;	/* Serialize access to users */
++	struct ipu6_dma_mapping *dmap;
++};
++
++struct ipu6_mmu {
++	struct list_head node;
++
++	struct ipu6_mmu_hw *mmu_hw;
++	unsigned int nr_mmus;
++	unsigned int mmid;
++
++	phys_addr_t pgtbl;
++	struct device *dev;
++
++	struct ipu6_dma_mapping *dmap;
++	struct list_head vma_list;
++
++	struct page *trash_page;
++	dma_addr_t pci_trash_page; /* IOVA from PCI DMA services (parent) */
++	dma_addr_t iova_trash_page; /* IOVA for IPU6 child nodes to use */
++
++	bool ready;
++	spinlock_t ready_lock;	/* Serialize access to bool ready */
++
++	void (*tlb_invalidate)(struct ipu6_mmu *mmu);
++};
++
++struct ipu6_mmu *ipu6_mmu_init(struct device *dev,
++			       void __iomem *base, int mmid,
++			       const struct ipu6_hw_variants *hw);
++void ipu6_mmu_cleanup(struct ipu6_mmu *mmu);
++int ipu6_mmu_hw_init(struct ipu6_mmu *mmu);
++void ipu6_mmu_hw_cleanup(struct ipu6_mmu *mmu);
++int ipu6_mmu_map(struct ipu6_mmu_info *mmu_info, unsigned long iova,
++		 phys_addr_t paddr, size_t size);
++size_t ipu6_mmu_unmap(struct ipu6_mmu_info *mmu_info, unsigned long iova,
++		      size_t size);
++phys_addr_t ipu6_mmu_iova_to_phys(struct ipu6_mmu_info *mmu_info,
++				  dma_addr_t iova);
++#endif
+-- 
+2.43.2
+
+
+From a4363013580d8a552e8084faedbb98bd2482c057 Mon Sep 17 00:00:00 2001
+From: Bingbu Cao <bingbu.cao@intel.com>
+Date: Thu, 11 Jan 2024 14:55:20 +0800
+Subject: [PATCH 13/33] media: intel/ipu6: add syscom interfaces between
+ firmware and driver
+
+Syscom is an inter-process(or) communication mechanism between an IPU
+and host. Syscom uses message queues for message exchange between IPU
+and host. Each message queue has its consumer and producer, host queue
+messages to firmware as the producer and then firmware to dequeue the
+messages as consumer and vice versa. IPU and host use shared registers
+or memory to reside the read and write indices which are updated by
+consumer and producer.
+
+Signed-off-by: Bingbu Cao <bingbu.cao@intel.com>
+---
+ drivers/media/pci/intel/ipu6/ipu6-fw-com.c | 413 +++++++++++++++++++++
+ drivers/media/pci/intel/ipu6/ipu6-fw-com.h |  47 +++
+ 2 files changed, 460 insertions(+)
+ create mode 100644 drivers/media/pci/intel/ipu6/ipu6-fw-com.c
+ create mode 100644 drivers/media/pci/intel/ipu6/ipu6-fw-com.h
+
+diff --git a/drivers/media/pci/intel/ipu6/ipu6-fw-com.c b/drivers/media/pci/intel/ipu6/ipu6-fw-com.c
+new file mode 100644
+index 000000000000..0f893f44e04c
+--- /dev/null
++++ b/drivers/media/pci/intel/ipu6/ipu6-fw-com.c
+@@ -0,0 +1,413 @@
++// SPDX-License-Identifier: GPL-2.0-only
++/*
++ * Copyright (C) 2013 - 2023 Intel Corporation
++ */
++
++#include <linux/device.h>
++#include <linux/dma-mapping.h>
++#include <linux/io.h>
++#include <linux/math.h>
++#include <linux/overflow.h>
++#include <linux/slab.h>
++#include <linux/types.h>
++
++#include "ipu6-bus.h"
++#include "ipu6-fw-com.h"
++
++/*
++ * FWCOM layer is a shared resource between FW and driver. It consist
++ * of token queues to both send and receive directions. Queue is simply
++ * an array of structures with read and write indexes to the queue.
++ * There are 1...n queues to both directions. Queues locates in
++ * system RAM and are mapped to ISP MMU so that both CPU and ISP can
++ * see the same buffer. Indexes are located in ISP DMEM so that FW code
++ * can poll those with very low latency and cost. CPU access to indexes is
++ * more costly but that happens only at message sending time and
++ * interrupt triggered message handling. CPU doesn't need to poll indexes.
++ * wr_reg / rd_reg are offsets to those dmem location. They are not
++ * the indexes itself.
++ */
++
++/* Shared structure between driver and FW - do not modify */
++struct ipu6_fw_sys_queue {
++	u64 host_address;
++	u32 vied_address;
++	u32 size;
++	u32 token_size;
++	u32 wr_reg;	/* reg number in subsystem's regmem */
++	u32 rd_reg;
++	u32 _align;
++} __packed;
++
++struct ipu6_fw_sys_queue_res {
++	u64 host_address;
++	u32 vied_address;
++	u32 reg;
++} __packed;
++
++enum syscom_state {
++	/* Program load or explicit host setting should init to this */
++	SYSCOM_STATE_UNINIT = 0x57a7e000,
++	/* SP Syscom sets this when it is ready for use */
++	SYSCOM_STATE_READY = 0x57a7e001,
++	/* SP Syscom sets this when no more syscom accesses will happen */
++	SYSCOM_STATE_INACTIVE = 0x57a7e002,
++};
++
++enum syscom_cmd {
++	/* Program load or explicit host setting should init to this */
++	SYSCOM_COMMAND_UNINIT = 0x57a7f000,
++	/* Host Syscom requests syscom to become inactive */
++	SYSCOM_COMMAND_INACTIVE = 0x57a7f001,
++};
++
++/* firmware config: data that sent from the host to SP via DDR */
++/* Cell copies data into a context */
++
++struct ipu6_fw_syscom_config {
++	u32 firmware_address;
++
++	u32 num_input_queues;
++	u32 num_output_queues;
++
++	/* ISP pointers to an array of ipu6_fw_sys_queue structures */
++	u32 input_queue;
++	u32 output_queue;
++
++	/* ISYS / PSYS private data */
++	u32 specific_addr;
++	u32 specific_size;
++};
++
++struct ipu6_fw_com_context {
++	struct ipu6_bus_device *adev;
++	void __iomem *dmem_addr;
++	int (*cell_ready)(struct ipu6_bus_device *adev);
++	void (*cell_start)(struct ipu6_bus_device *adev);
++
++	void *dma_buffer;
++	dma_addr_t dma_addr;
++	unsigned int dma_size;
++	unsigned long attrs;
++
++	struct ipu6_fw_sys_queue *input_queue;	/* array of host to SP queues */
++	struct ipu6_fw_sys_queue *output_queue;	/* array of SP to host */
++
++	u32 config_vied_addr;
++
++	unsigned int buttress_boot_offset;
++	void __iomem *base_addr;
++};
++
++#define FW_COM_WR_REG 0
++#define FW_COM_RD_REG 4
++
++#define REGMEM_OFFSET 0
++#define TUNIT_MAGIC_PATTERN 0x5a5a5a5a
++
++enum regmem_id {
++	/* pass pkg_dir address to SPC in non-secure mode */
++	PKG_DIR_ADDR_REG = 0,
++	/* Tunit CFG blob for secure - provided by host.*/
++	TUNIT_CFG_DWR_REG = 1,
++	/* syscom commands - modified by the host */
++	SYSCOM_COMMAND_REG = 2,
++	/* Store interrupt status - updated by SP */
++	SYSCOM_IRQ_REG = 3,
++	/* first syscom queue pointer register */
++	SYSCOM_QPR_BASE_REG = 4
++};
++
++#define BUTTRESS_FW_BOOT_PARAMS_0 0x4000
++#define BUTTRESS_FW_BOOT_PARAM_REG(base, offset, id)			\
++	((base) + BUTTRESS_FW_BOOT_PARAMS_0 + ((offset) + (id)) * 4)
++
++enum buttress_syscom_id {
++	/* pass syscom configuration to SPC */
++	SYSCOM_CONFIG_ID		= 0,
++	/* syscom state - modified by SP */
++	SYSCOM_STATE_ID			= 1,
++	/* syscom vtl0 addr mask */
++	SYSCOM_VTL0_ADDR_MASK_ID	= 2,
++	SYSCOM_ID_MAX
++};
++
++static void ipu6_sys_queue_init(struct ipu6_fw_sys_queue *q, unsigned int size,
++				unsigned int token_size,
++				struct ipu6_fw_sys_queue_res *res)
++{
++	unsigned int buf_size = (size + 1) * token_size;
++
++	q->size = size + 1;
++	q->token_size = token_size;
++
++	/* acquire the shared buffer space */
++	q->host_address = res->host_address;
++	res->host_address += buf_size;
++	q->vied_address = res->vied_address;
++	res->vied_address += buf_size;
++
++	/* acquire the shared read and writer pointers */
++	q->wr_reg = res->reg;
++	res->reg++;
++	q->rd_reg = res->reg;
++	res->reg++;
++}
++
++void *ipu6_fw_com_prepare(struct ipu6_fw_com_cfg *cfg,
++			  struct ipu6_bus_device *adev, void __iomem *base)
++{
++	size_t conf_size, inq_size, outq_size, specific_size;
++	struct ipu6_fw_syscom_config *config_host_addr;
++	unsigned int sizeinput = 0, sizeoutput = 0;
++	struct ipu6_fw_sys_queue_res res;
++	struct ipu6_fw_com_context *ctx;
++	struct device *dev = &adev->auxdev.dev;
++	size_t sizeall, offset;
++	unsigned long attrs = 0;
++	void *specific_host_addr;
++	unsigned int i;
++
++	if (!cfg || !cfg->cell_start || !cfg->cell_ready)
++		return NULL;
++
++	ctx = kzalloc(sizeof(*ctx), GFP_KERNEL);
++	if (!ctx)
++		return NULL;
++	ctx->dmem_addr = base + cfg->dmem_addr + REGMEM_OFFSET;
++	ctx->adev = adev;
++	ctx->cell_start = cfg->cell_start;
++	ctx->cell_ready = cfg->cell_ready;
++	ctx->buttress_boot_offset = cfg->buttress_boot_offset;
++	ctx->base_addr  = base;
++
++	/*
++	 * Allocate DMA mapped memory. Allocate one big chunk.
++	 */
++	/* Base cfg for FW */
++	conf_size = roundup(sizeof(struct ipu6_fw_syscom_config), 8);
++	/* Descriptions of the queues */
++	inq_size = size_mul(cfg->num_input_queues,
++			    sizeof(struct ipu6_fw_sys_queue));
++	outq_size = size_mul(cfg->num_output_queues,
++			     sizeof(struct ipu6_fw_sys_queue));
++	/* FW specific information structure */
++	specific_size = roundup(cfg->specific_size, 8);
++
++	sizeall = conf_size + inq_size + outq_size + specific_size;
++
++	for (i = 0; i < cfg->num_input_queues; i++)
++		sizeinput += size_mul(cfg->input[i].queue_size + 1,
++				      cfg->input[i].token_size);
++
++	for (i = 0; i < cfg->num_output_queues; i++)
++		sizeoutput += size_mul(cfg->output[i].queue_size + 1,
++				       cfg->output[i].token_size);
++
++	sizeall += sizeinput + sizeoutput;
++
++	ctx->dma_buffer = dma_alloc_attrs(dev, sizeall, &ctx->dma_addr,
++					  GFP_KERNEL, attrs);
++	ctx->attrs = attrs;
++	if (!ctx->dma_buffer) {
++		dev_err(dev, "failed to allocate dma memory\n");
++		kfree(ctx);
++		return NULL;
++	}
++
++	ctx->dma_size = sizeall;
++
++	config_host_addr = ctx->dma_buffer;
++	ctx->config_vied_addr = ctx->dma_addr;
++
++	offset = conf_size;
++	ctx->input_queue = ctx->dma_buffer + offset;
++	config_host_addr->input_queue = ctx->dma_addr + offset;
++	config_host_addr->num_input_queues = cfg->num_input_queues;
++
++	offset += inq_size;
++	ctx->output_queue = ctx->dma_buffer + offset;
++	config_host_addr->output_queue = ctx->dma_addr + offset;
++	config_host_addr->num_output_queues = cfg->num_output_queues;
++
++	/* copy firmware specific data */
++	offset += outq_size;
++	specific_host_addr = ctx->dma_buffer + offset;
++	config_host_addr->specific_addr = ctx->dma_addr + offset;
++	config_host_addr->specific_size = cfg->specific_size;
++	if (cfg->specific_addr && cfg->specific_size)
++		memcpy(specific_host_addr, cfg->specific_addr,
++		       cfg->specific_size);
++
++	/* initialize input queues */
++	offset += specific_size;
++	res.reg = SYSCOM_QPR_BASE_REG;
++	res.host_address = (u64)(ctx->dma_buffer + offset);
++	res.vied_address = ctx->dma_addr + offset;
++	for (i = 0; i < cfg->num_input_queues; i++)
++		ipu6_sys_queue_init(ctx->input_queue + i,
++				    cfg->input[i].queue_size,
++				    cfg->input[i].token_size, &res);
++
++	/* initialize output queues */
++	offset += sizeinput;
++	res.host_address = (u64)(ctx->dma_buffer + offset);
++	res.vied_address = ctx->dma_addr + offset;
++	for (i = 0; i < cfg->num_output_queues; i++) {
++		ipu6_sys_queue_init(ctx->output_queue + i,
++				    cfg->output[i].queue_size,
++				    cfg->output[i].token_size, &res);
++	}
++
++	return ctx;
++}
++EXPORT_SYMBOL_NS_GPL(ipu6_fw_com_prepare, INTEL_IPU6);
++
++int ipu6_fw_com_open(struct ipu6_fw_com_context *ctx)
++{
++	/* write magic pattern to disable the tunit trace */
++	writel(TUNIT_MAGIC_PATTERN, ctx->dmem_addr + TUNIT_CFG_DWR_REG * 4);
++	/* Check if SP is in valid state */
++	if (!ctx->cell_ready(ctx->adev))
++		return -EIO;
++
++	/* store syscom uninitialized command */
++	writel(SYSCOM_COMMAND_UNINIT, ctx->dmem_addr + SYSCOM_COMMAND_REG * 4);
++
++	/* store syscom uninitialized state */
++	writel(SYSCOM_STATE_UNINIT,
++	       BUTTRESS_FW_BOOT_PARAM_REG(ctx->base_addr,
++					  ctx->buttress_boot_offset,
++					  SYSCOM_STATE_ID));
++
++	/* store firmware configuration address */
++	writel(ctx->config_vied_addr,
++	       BUTTRESS_FW_BOOT_PARAM_REG(ctx->base_addr,
++					  ctx->buttress_boot_offset,
++					  SYSCOM_CONFIG_ID));
++	ctx->cell_start(ctx->adev);
++
++	return 0;
++}
++EXPORT_SYMBOL_NS_GPL(ipu6_fw_com_open, INTEL_IPU6);
++
++int ipu6_fw_com_close(struct ipu6_fw_com_context *ctx)
++{
++	int state;
++
++	state = readl(BUTTRESS_FW_BOOT_PARAM_REG(ctx->base_addr,
++						 ctx->buttress_boot_offset,
++						 SYSCOM_STATE_ID));
++	if (state != SYSCOM_STATE_READY)
++		return -EBUSY;
++
++	/* set close request flag */
++	writel(SYSCOM_COMMAND_INACTIVE, ctx->dmem_addr +
++	       SYSCOM_COMMAND_REG * 4);
++
++	return 0;
++}
++EXPORT_SYMBOL_NS_GPL(ipu6_fw_com_close, INTEL_IPU6);
++
++int ipu6_fw_com_release(struct ipu6_fw_com_context *ctx, unsigned int force)
++{
++	/* check if release is forced, an verify cell state if it is not */
++	if (!force && !ctx->cell_ready(ctx->adev))
++		return -EBUSY;
++
++	dma_free_attrs(&ctx->adev->auxdev.dev, ctx->dma_size,
++		       ctx->dma_buffer, ctx->dma_addr, ctx->attrs);
++	kfree(ctx);
++	return 0;
++}
++EXPORT_SYMBOL_NS_GPL(ipu6_fw_com_release, INTEL_IPU6);
++
++bool ipu6_fw_com_ready(struct ipu6_fw_com_context *ctx)
++{
++	int state;
++
++	state = readl(BUTTRESS_FW_BOOT_PARAM_REG(ctx->base_addr,
++						 ctx->buttress_boot_offset,
++						 SYSCOM_STATE_ID));
++
++	return state == SYSCOM_STATE_READY;
++}
++EXPORT_SYMBOL_NS_GPL(ipu6_fw_com_ready, INTEL_IPU6);
++
++void *ipu6_send_get_token(struct ipu6_fw_com_context *ctx, int q_nbr)
++{
++	struct ipu6_fw_sys_queue *q = &ctx->input_queue[q_nbr];
++	void __iomem *q_dmem = ctx->dmem_addr + q->wr_reg * 4;
++	unsigned int wr, rd;
++	unsigned int packets;
++	unsigned int index;
++
++	wr = readl(q_dmem + FW_COM_WR_REG);
++	rd = readl(q_dmem + FW_COM_RD_REG);
++
++	if (WARN_ON_ONCE(wr >= q->size || rd >= q->size))
++		return NULL;
++
++	if (wr < rd)
++		packets = rd - wr - 1;
++	else
++		packets = q->size - (wr - rd + 1);
++
++	if (!packets)
++		return NULL;
++
++	index = readl(q_dmem + FW_COM_WR_REG);
++
++	return (void *)(q->host_address + index * q->token_size);
++}
++EXPORT_SYMBOL_NS_GPL(ipu6_send_get_token, INTEL_IPU6);
++
++void ipu6_send_put_token(struct ipu6_fw_com_context *ctx, int q_nbr)
++{
++	struct ipu6_fw_sys_queue *q = &ctx->input_queue[q_nbr];
++	void __iomem *q_dmem = ctx->dmem_addr + q->wr_reg * 4;
++	unsigned int wr = readl(q_dmem + FW_COM_WR_REG) + 1;
++
++	if (wr >= q->size)
++		wr = 0;
++
++	writel(wr, q_dmem + FW_COM_WR_REG);
++}
++EXPORT_SYMBOL_NS_GPL(ipu6_send_put_token, INTEL_IPU6);
++
++void *ipu6_recv_get_token(struct ipu6_fw_com_context *ctx, int q_nbr)
++{
++	struct ipu6_fw_sys_queue *q = &ctx->output_queue[q_nbr];
++	void __iomem *q_dmem = ctx->dmem_addr + q->wr_reg * 4;
++	unsigned int wr, rd;
++	unsigned int packets;
++
++	wr = readl(q_dmem + FW_COM_WR_REG);
++	rd = readl(q_dmem + FW_COM_RD_REG);
++
++	if (WARN_ON_ONCE(wr >= q->size || rd >= q->size))
++		return NULL;
++
++	if (wr < rd)
++		wr += q->size;
++
++	packets = wr - rd;
++	if (!packets)
++		return NULL;
++
++	return (void *)(q->host_address + rd * q->token_size);
++}
++EXPORT_SYMBOL_NS_GPL(ipu6_recv_get_token, INTEL_IPU6);
++
++void ipu6_recv_put_token(struct ipu6_fw_com_context *ctx, int q_nbr)
++{
++	struct ipu6_fw_sys_queue *q = &ctx->output_queue[q_nbr];
++	void __iomem *q_dmem = ctx->dmem_addr + q->wr_reg * 4;
++	unsigned int rd = readl(q_dmem + FW_COM_RD_REG) + 1;
++
++	if (rd >= q->size)
++		rd = 0;
++
++	writel(rd, q_dmem + FW_COM_RD_REG);
++}
++EXPORT_SYMBOL_NS_GPL(ipu6_recv_put_token, INTEL_IPU6);
+diff --git a/drivers/media/pci/intel/ipu6/ipu6-fw-com.h b/drivers/media/pci/intel/ipu6/ipu6-fw-com.h
+new file mode 100644
+index 000000000000..660c406b3ac9
+--- /dev/null
++++ b/drivers/media/pci/intel/ipu6/ipu6-fw-com.h
+@@ -0,0 +1,47 @@
++/* SPDX-License-Identifier: GPL-2.0-only */
++/* Copyright (C) 2013 - 2023 Intel Corporation */
++
++#ifndef IPU6_FW_COM_H
++#define IPU6_FW_COM_H
++
++struct ipu6_fw_com_context;
++struct ipu6_bus_device;
++
++struct ipu6_fw_syscom_queue_config {
++	unsigned int queue_size;	/* tokens per queue */
++	unsigned int token_size;	/* bytes per token */
++};
++
++#define SYSCOM_BUTTRESS_FW_PARAMS_ISYS_OFFSET	0
++
++struct ipu6_fw_com_cfg {
++	unsigned int num_input_queues;
++	unsigned int num_output_queues;
++	struct ipu6_fw_syscom_queue_config *input;
++	struct ipu6_fw_syscom_queue_config *output;
++
++	unsigned int dmem_addr;
++
++	/* firmware-specific configuration data */
++	void *specific_addr;
++	unsigned int specific_size;
++	int (*cell_ready)(struct ipu6_bus_device *adev);
++	void (*cell_start)(struct ipu6_bus_device *adev);
++
++	unsigned int buttress_boot_offset;
++};
++
++void *ipu6_fw_com_prepare(struct ipu6_fw_com_cfg *cfg,
++			  struct ipu6_bus_device *adev, void __iomem *base);
++
++int ipu6_fw_com_open(struct ipu6_fw_com_context *ctx);
++bool ipu6_fw_com_ready(struct ipu6_fw_com_context *ctx);
++int ipu6_fw_com_close(struct ipu6_fw_com_context *ctx);
++int ipu6_fw_com_release(struct ipu6_fw_com_context *ctx, unsigned int force);
++
++void *ipu6_recv_get_token(struct ipu6_fw_com_context *ctx, int q_nbr);
++void ipu6_recv_put_token(struct ipu6_fw_com_context *ctx, int q_nbr);
++void *ipu6_send_get_token(struct ipu6_fw_com_context *ctx, int q_nbr);
++void ipu6_send_put_token(struct ipu6_fw_com_context *ctx, int q_nbr);
++
++#endif
+-- 
+2.43.2
+
+
+From e690b8a96eb4fbe1d948ad80047a9af7c601b761 Mon Sep 17 00:00:00 2001
+From: Bingbu Cao <bingbu.cao@intel.com>
+Date: Thu, 11 Jan 2024 14:55:21 +0800
+Subject: [PATCH 14/33] media: intel/ipu6: input system ABI between firmware
+ and driver
+
+Implement the input system firmware ABIs between the firmware and
+driver - include stream configuration, control command, capture
+request and response, etc.
+
+Signed-off-by: Bingbu Cao <bingbu.cao@intel.com>
+---
+ drivers/media/pci/intel/ipu6/ipu6-fw-isys.c | 487 +++++++++++++++++
+ drivers/media/pci/intel/ipu6/ipu6-fw-isys.h | 573 ++++++++++++++++++++
+ 2 files changed, 1060 insertions(+)
+ create mode 100644 drivers/media/pci/intel/ipu6/ipu6-fw-isys.c
+ create mode 100644 drivers/media/pci/intel/ipu6/ipu6-fw-isys.h
+
+diff --git a/drivers/media/pci/intel/ipu6/ipu6-fw-isys.c b/drivers/media/pci/intel/ipu6/ipu6-fw-isys.c
+new file mode 100644
+index 000000000000..e06c1c955d38
+--- /dev/null
++++ b/drivers/media/pci/intel/ipu6/ipu6-fw-isys.c
+@@ -0,0 +1,487 @@
++// SPDX-License-Identifier: GPL-2.0-only
++/*
++ * Copyright (C) 2013 - 2023 Intel Corporation
++ */
++
++#include <linux/cacheflush.h>
++#include <linux/delay.h>
++#include <linux/device.h>
++#include <linux/io.h>
++#include <linux/spinlock.h>
++#include <linux/types.h>
++
++#include "ipu6-bus.h"
++#include "ipu6-fw-com.h"
++#include "ipu6-isys.h"
++#include "ipu6-platform-isys-csi2-reg.h"
++#include "ipu6-platform-regs.h"
++
++static const char send_msg_types[N_IPU6_FW_ISYS_SEND_TYPE][32] = {
++	"STREAM_OPEN",
++	"STREAM_START",
++	"STREAM_START_AND_CAPTURE",
++	"STREAM_CAPTURE",
++	"STREAM_STOP",
++	"STREAM_FLUSH",
++	"STREAM_CLOSE"
++};
++
++static int handle_proxy_response(struct ipu6_isys *isys, unsigned int req_id)
++{
++	struct device *dev = &isys->adev->auxdev.dev;
++	struct ipu6_fw_isys_proxy_resp_info_abi *resp;
++	int ret;
++
++	resp = ipu6_recv_get_token(isys->fwcom, IPU6_BASE_PROXY_RECV_QUEUES);
++	if (!resp)
++		return 1;
++
++	dev_dbg(dev, "Proxy response: id %u, error %u, details %u\n",
++		resp->request_id, resp->error_info.error,
++		resp->error_info.error_details);
++
++	ret = req_id == resp->request_id ? 0 : -EIO;
++
++	ipu6_recv_put_token(isys->fwcom, IPU6_BASE_PROXY_RECV_QUEUES);
++
++	return ret;
++}
++
++int ipu6_fw_isys_send_proxy_token(struct ipu6_isys *isys,
++				  unsigned int req_id,
++				  unsigned int index,
++				  unsigned int offset, u32 value)
++{
++	struct ipu6_fw_com_context *ctx = isys->fwcom;
++	struct device *dev = &isys->adev->auxdev.dev;
++	struct ipu6_fw_proxy_send_queue_token *token;
++	unsigned int timeout = 1000;
++	int ret;
++
++	dev_dbg(dev,
++		"proxy send: req_id 0x%x, index %d, offset 0x%x, value 0x%x\n",
++		req_id, index, offset, value);
++
++	token = ipu6_send_get_token(ctx, IPU6_BASE_PROXY_SEND_QUEUES);
++	if (!token)
++		return -EBUSY;
++
++	token->request_id = req_id;
++	token->region_index = index;
++	token->offset = offset;
++	token->value = value;
++	ipu6_send_put_token(ctx, IPU6_BASE_PROXY_SEND_QUEUES);
++
++	do {
++		usleep_range(100, 110);
++		ret = handle_proxy_response(isys, req_id);
++		if (!ret)
++			break;
++		if (ret == -EIO) {
++			dev_err(dev, "Proxy respond with unexpected id\n");
++			break;
++		}
++		timeout--;
++	} while (ret && timeout);
++
++	if (!timeout)
++		dev_err(dev, "Proxy response timed out\n");
++
++	return ret;
++}
++
++int ipu6_fw_isys_complex_cmd(struct ipu6_isys *isys,
++			     const unsigned int stream_handle,
++			     void *cpu_mapped_buf,
++			     dma_addr_t dma_mapped_buf,
++			     size_t size, u16 send_type)
++{
++	struct ipu6_fw_com_context *ctx = isys->fwcom;
++	struct device *dev = &isys->adev->auxdev.dev;
++	struct ipu6_fw_send_queue_token *token;
++
++	if (send_type >= N_IPU6_FW_ISYS_SEND_TYPE)
++		return -EINVAL;
++
++	dev_dbg(dev, "send_token: %s\n", send_msg_types[send_type]);
++
++	/*
++	 * Time to flush cache in case we have some payload. Not all messages
++	 * have that
++	 */
++	if (cpu_mapped_buf)
++		clflush_cache_range(cpu_mapped_buf, size);
++
++	token = ipu6_send_get_token(ctx,
++				    stream_handle + IPU6_BASE_MSG_SEND_QUEUES);
++	if (!token)
++		return -EBUSY;
++
++	token->payload = dma_mapped_buf;
++	token->buf_handle = (unsigned long)cpu_mapped_buf;
++	token->send_type = send_type;
++
++	ipu6_send_put_token(ctx, stream_handle + IPU6_BASE_MSG_SEND_QUEUES);
++
++	return 0;
++}
++
++int ipu6_fw_isys_simple_cmd(struct ipu6_isys *isys,
++			    const unsigned int stream_handle, u16 send_type)
++{
++	return ipu6_fw_isys_complex_cmd(isys, stream_handle, NULL, 0, 0,
++					send_type);
++}
++
++int ipu6_fw_isys_close(struct ipu6_isys *isys)
++{
++	struct device *dev = &isys->adev->auxdev.dev;
++	int retry = IPU6_ISYS_CLOSE_RETRY;
++	unsigned long flags;
++	void *fwcom;
++	int ret;
++
++	/*
++	 * Stop the isys fw. Actual close takes
++	 * some time as the FW must stop its actions including code fetch
++	 * to SP icache.
++	 * spinlock to wait the interrupt handler to be finished
++	 */
++	spin_lock_irqsave(&isys->power_lock, flags);
++	ret = ipu6_fw_com_close(isys->fwcom);
++	fwcom = isys->fwcom;
++	isys->fwcom = NULL;
++	spin_unlock_irqrestore(&isys->power_lock, flags);
++	if (ret)
++		dev_err(dev, "Device close failure: %d\n", ret);
++
++	/* release probably fails if the close failed. Let's try still */
++	do {
++		usleep_range(400, 500);
++		ret = ipu6_fw_com_release(fwcom, 0);
++		retry--;
++	} while (ret && retry);
++
++	if (ret) {
++		dev_err(dev, "Device release time out %d\n", ret);
++		spin_lock_irqsave(&isys->power_lock, flags);
++		isys->fwcom = fwcom;
++		spin_unlock_irqrestore(&isys->power_lock, flags);
++	}
++
++	return ret;
++}
++
++void ipu6_fw_isys_cleanup(struct ipu6_isys *isys)
++{
++	int ret;
++
++	ret = ipu6_fw_com_release(isys->fwcom, 1);
++	if (ret < 0)
++		dev_warn(&isys->adev->auxdev.dev,
++			 "Device busy, fw_com release failed.");
++	isys->fwcom = NULL;
++}
++
++static void start_sp(struct ipu6_bus_device *adev)
++{
++	struct ipu6_isys *isys = ipu6_bus_get_drvdata(adev);
++	void __iomem *spc_regs_base = isys->pdata->base +
++		isys->pdata->ipdata->hw_variant.spc_offset;
++	u32 val = IPU6_ISYS_SPC_STATUS_START |
++		IPU6_ISYS_SPC_STATUS_RUN |
++		IPU6_ISYS_SPC_STATUS_CTRL_ICACHE_INVALIDATE;
++
++	val |= isys->icache_prefetch ? IPU6_ISYS_SPC_STATUS_ICACHE_PREFETCH : 0;
++
++	writel(val, spc_regs_base + IPU6_ISYS_REG_SPC_STATUS_CTRL);
++}
++
++static int query_sp(struct ipu6_bus_device *adev)
++{
++	struct ipu6_isys *isys = ipu6_bus_get_drvdata(adev);
++	void __iomem *spc_regs_base = isys->pdata->base +
++		isys->pdata->ipdata->hw_variant.spc_offset;
++	u32 val;
++
++	val = readl(spc_regs_base + IPU6_ISYS_REG_SPC_STATUS_CTRL);
++	/* return true when READY == 1, START == 0 */
++	val &= IPU6_ISYS_SPC_STATUS_READY | IPU6_ISYS_SPC_STATUS_START;
++
++	return val == IPU6_ISYS_SPC_STATUS_READY;
++}
++
++static int ipu6_isys_fwcom_cfg_init(struct ipu6_isys *isys,
++				    struct ipu6_fw_com_cfg *fwcom,
++				    unsigned int num_streams)
++{
++	unsigned int max_send_queues, max_sram_blocks, max_devq_size;
++	struct ipu6_fw_syscom_queue_config *input_queue_cfg;
++	struct ipu6_fw_syscom_queue_config *output_queue_cfg;
++	struct device *dev = &isys->adev->auxdev.dev;
++	int type_proxy = IPU6_FW_ISYS_QUEUE_TYPE_PROXY;
++	int type_dev = IPU6_FW_ISYS_QUEUE_TYPE_DEV;
++	int type_msg = IPU6_FW_ISYS_QUEUE_TYPE_MSG;
++	int base_dev_send = IPU6_BASE_DEV_SEND_QUEUES;
++	int base_msg_send = IPU6_BASE_MSG_SEND_QUEUES;
++	int base_msg_recv = IPU6_BASE_MSG_RECV_QUEUES;
++	struct ipu6_fw_isys_fw_config *isys_fw_cfg;
++	u32 num_in_message_queues;
++	unsigned int max_streams;
++	unsigned int size;
++	unsigned int i;
++
++	max_streams = isys->pdata->ipdata->max_streams;
++	max_send_queues = isys->pdata->ipdata->max_send_queues;
++	max_sram_blocks = isys->pdata->ipdata->max_sram_blocks;
++	max_devq_size = isys->pdata->ipdata->max_devq_size;
++	num_in_message_queues = clamp(num_streams, 1U, max_streams);
++	isys_fw_cfg = devm_kzalloc(dev, sizeof(*isys_fw_cfg), GFP_KERNEL);
++	if (!isys_fw_cfg)
++		return -ENOMEM;
++
++	isys_fw_cfg->num_send_queues[type_proxy] = IPU6_N_MAX_PROXY_SEND_QUEUES;
++	isys_fw_cfg->num_send_queues[type_dev] = IPU6_N_MAX_DEV_SEND_QUEUES;
++	isys_fw_cfg->num_send_queues[type_msg] = num_in_message_queues;
++	isys_fw_cfg->num_recv_queues[type_proxy] = IPU6_N_MAX_PROXY_RECV_QUEUES;
++	/* Common msg/dev return queue */
++	isys_fw_cfg->num_recv_queues[type_dev] = 0;
++	isys_fw_cfg->num_recv_queues[type_msg] = 1;
++
++	size = sizeof(*input_queue_cfg) * max_send_queues;
++	input_queue_cfg = devm_kzalloc(dev, size, GFP_KERNEL);
++	if (!input_queue_cfg)
++		return -ENOMEM;
++
++	size = sizeof(*output_queue_cfg) * IPU6_N_MAX_RECV_QUEUES;
++	output_queue_cfg = devm_kzalloc(dev, size, GFP_KERNEL);
++	if (!output_queue_cfg)
++		return -ENOMEM;
++
++	fwcom->input = input_queue_cfg;
++	fwcom->output = output_queue_cfg;
++
++	fwcom->num_input_queues = isys_fw_cfg->num_send_queues[type_proxy] +
++		isys_fw_cfg->num_send_queues[type_dev] +
++		isys_fw_cfg->num_send_queues[type_msg];
++
++	fwcom->num_output_queues = isys_fw_cfg->num_recv_queues[type_proxy] +
++		isys_fw_cfg->num_recv_queues[type_dev] +
++		isys_fw_cfg->num_recv_queues[type_msg];
++
++	/* SRAM partitioning. Equal partitioning is set. */
++	for (i = 0; i < max_sram_blocks; i++) {
++		if (i < num_in_message_queues)
++			isys_fw_cfg->buffer_partition.num_gda_pages[i] =
++				(IPU6_DEVICE_GDA_NR_PAGES *
++				 IPU6_DEVICE_GDA_VIRT_FACTOR) /
++				num_in_message_queues;
++		else
++			isys_fw_cfg->buffer_partition.num_gda_pages[i] = 0;
++	}
++
++	/* FW assumes proxy interface at fwcom queue 0 */
++	for (i = 0; i < isys_fw_cfg->num_send_queues[type_proxy]; i++) {
++		input_queue_cfg[i].token_size =
++			sizeof(struct ipu6_fw_proxy_send_queue_token);
++		input_queue_cfg[i].queue_size = IPU6_ISYS_SIZE_PROXY_SEND_QUEUE;
++	}
++
++	for (i = 0; i < isys_fw_cfg->num_send_queues[type_dev]; i++) {
++		input_queue_cfg[base_dev_send + i].token_size =
++			sizeof(struct ipu6_fw_send_queue_token);
++		input_queue_cfg[base_dev_send + i].queue_size = max_devq_size;
++	}
++
++	for (i = 0; i < isys_fw_cfg->num_send_queues[type_msg]; i++) {
++		input_queue_cfg[base_msg_send + i].token_size =
++			sizeof(struct ipu6_fw_send_queue_token);
++		input_queue_cfg[base_msg_send + i].queue_size =
++			IPU6_ISYS_SIZE_SEND_QUEUE;
++	}
++
++	for (i = 0; i < isys_fw_cfg->num_recv_queues[type_proxy]; i++) {
++		output_queue_cfg[i].token_size =
++			sizeof(struct ipu6_fw_proxy_resp_queue_token);
++		output_queue_cfg[i].queue_size =
++			IPU6_ISYS_SIZE_PROXY_RECV_QUEUE;
++	}
++	/* There is no recv DEV queue */
++	for (i = 0; i < isys_fw_cfg->num_recv_queues[type_msg]; i++) {
++		output_queue_cfg[base_msg_recv + i].token_size =
++			sizeof(struct ipu6_fw_resp_queue_token);
++		output_queue_cfg[base_msg_recv + i].queue_size =
++			IPU6_ISYS_SIZE_RECV_QUEUE;
++	}
++
++	fwcom->dmem_addr = isys->pdata->ipdata->hw_variant.dmem_offset;
++	fwcom->specific_addr = isys_fw_cfg;
++	fwcom->specific_size = sizeof(*isys_fw_cfg);
++
++	return 0;
++}
++
++int ipu6_fw_isys_init(struct ipu6_isys *isys, unsigned int num_streams)
++{
++	struct device *dev = &isys->adev->auxdev.dev;
++	int retry = IPU6_ISYS_OPEN_RETRY;
++	struct ipu6_fw_com_cfg fwcom = {
++		.cell_start = start_sp,
++		.cell_ready = query_sp,
++		.buttress_boot_offset = SYSCOM_BUTTRESS_FW_PARAMS_ISYS_OFFSET,
++	};
++	int ret;
++
++	ipu6_isys_fwcom_cfg_init(isys, &fwcom, num_streams);
++
++	isys->fwcom = ipu6_fw_com_prepare(&fwcom, isys->adev,
++					  isys->pdata->base);
++	if (!isys->fwcom) {
++		dev_err(dev, "isys fw com prepare failed\n");
++		return -EIO;
++	}
++
++	ret = ipu6_fw_com_open(isys->fwcom);
++	if (ret) {
++		dev_err(dev, "isys fw com open failed %d\n", ret);
++		return ret;
++	}
++
++	do {
++		usleep_range(400, 500);
++		if (ipu6_fw_com_ready(isys->fwcom))
++			break;
++		retry--;
++	} while (retry > 0);
++
++	if (!retry) {
++		dev_err(dev, "isys port open ready failed %d\n", ret);
++		ipu6_fw_isys_close(isys);
++		ret = -EIO;
++	}
++
++	return ret;
++}
++
++struct ipu6_fw_isys_resp_info_abi *
++ipu6_fw_isys_get_resp(void *context, unsigned int queue)
++{
++	return ipu6_recv_get_token(context, queue);
++}
++
++void ipu6_fw_isys_put_resp(void *context, unsigned int queue)
++{
++	ipu6_recv_put_token(context, queue);
++}
++
++void ipu6_fw_isys_dump_stream_cfg(struct device *dev,
++				  struct ipu6_fw_isys_stream_cfg_data_abi *cfg)
++{
++	unsigned int i;
++
++	dev_dbg(dev, "-----------------------------------------------------\n");
++	dev_dbg(dev, "IPU6_FW_ISYS_STREAM_CFG_DATA\n");
++
++	dev_dbg(dev, "compfmt = %d\n", cfg->vc);
++	dev_dbg(dev, "src = %d\n", cfg->src);
++	dev_dbg(dev, "vc = %d\n", cfg->vc);
++	dev_dbg(dev, "isl_use = %d\n", cfg->isl_use);
++	dev_dbg(dev, "sensor_type = %d\n", cfg->sensor_type);
++
++	dev_dbg(dev, "send_irq_sof_discarded = %d\n",
++		cfg->send_irq_sof_discarded);
++	dev_dbg(dev, "send_irq_eof_discarded = %d\n",
++		cfg->send_irq_eof_discarded);
++	dev_dbg(dev, "send_resp_sof_discarded = %d\n",
++		cfg->send_resp_sof_discarded);
++	dev_dbg(dev, "send_resp_eof_discarded = %d\n",
++		cfg->send_resp_eof_discarded);
++
++	dev_dbg(dev, "crop:\n");
++	dev_dbg(dev, "\t.left_top = [%d, %d]\n", cfg->crop.left_offset,
++		cfg->crop.top_offset);
++	dev_dbg(dev, "\t.right_bottom = [%d, %d]\n", cfg->crop.right_offset,
++		cfg->crop.bottom_offset);
++
++	dev_dbg(dev, "nof_input_pins = %d\n", cfg->nof_input_pins);
++	for (i = 0; i < cfg->nof_input_pins; i++) {
++		dev_dbg(dev, "input pin[%d]:\n", i);
++		dev_dbg(dev, "\t.dt = 0x%0x\n", cfg->input_pins[i].dt);
++		dev_dbg(dev, "\t.mipi_store_mode = %d\n",
++			cfg->input_pins[i].mipi_store_mode);
++		dev_dbg(dev, "\t.bits_per_pix = %d\n",
++			cfg->input_pins[i].bits_per_pix);
++		dev_dbg(dev, "\t.mapped_dt = 0x%0x\n",
++			cfg->input_pins[i].mapped_dt);
++		dev_dbg(dev, "\t.input_res = %dx%d\n",
++			cfg->input_pins[i].input_res.width,
++			cfg->input_pins[i].input_res.height);
++		dev_dbg(dev, "\t.mipi_decompression = %d\n",
++			cfg->input_pins[i].mipi_decompression);
++		dev_dbg(dev, "\t.capture_mode = %d\n",
++			cfg->input_pins[i].capture_mode);
++	}
++
++	dev_dbg(dev, "nof_output_pins = %d\n", cfg->nof_output_pins);
++	for (i = 0; i < cfg->nof_output_pins; i++) {
++		dev_dbg(dev, "output_pin[%d]:\n", i);
++		dev_dbg(dev, "\t.input_pin_id = %d\n",
++			cfg->output_pins[i].input_pin_id);
++		dev_dbg(dev, "\t.output_res = %dx%d\n",
++			cfg->output_pins[i].output_res.width,
++			cfg->output_pins[i].output_res.height);
++		dev_dbg(dev, "\t.stride = %d\n", cfg->output_pins[i].stride);
++		dev_dbg(dev, "\t.pt = %d\n", cfg->output_pins[i].pt);
++		dev_dbg(dev, "\t.payload_buf_size = %d\n",
++			cfg->output_pins[i].payload_buf_size);
++		dev_dbg(dev, "\t.ft = %d\n", cfg->output_pins[i].ft);
++		dev_dbg(dev, "\t.watermark_in_lines = %d\n",
++			cfg->output_pins[i].watermark_in_lines);
++		dev_dbg(dev, "\t.send_irq = %d\n",
++			cfg->output_pins[i].send_irq);
++		dev_dbg(dev, "\t.reserve_compression = %d\n",
++			cfg->output_pins[i].reserve_compression);
++		dev_dbg(dev, "\t.snoopable = %d\n",
++			cfg->output_pins[i].snoopable);
++		dev_dbg(dev, "\t.error_handling_enable = %d\n",
++			cfg->output_pins[i].error_handling_enable);
++		dev_dbg(dev, "\t.sensor_type = %d\n",
++			cfg->output_pins[i].sensor_type);
++	}
++	dev_dbg(dev, "-----------------------------------------------------\n");
++}
++
++void
++ipu6_fw_isys_dump_frame_buff_set(struct device *dev,
++				 struct ipu6_fw_isys_frame_buff_set_abi *buf,
++				 unsigned int outputs)
++{
++	unsigned int i;
++
++	dev_dbg(dev, "-----------------------------------------------------\n");
++	dev_dbg(dev, "IPU6_FW_ISYS_FRAME_BUFF_SET\n");
++
++	for (i = 0; i < outputs; i++) {
++		dev_dbg(dev, "output_pin[%d]:\n", i);
++		dev_dbg(dev, "\t.out_buf_id = %llu\n",
++			buf->output_pins[i].out_buf_id);
++		dev_dbg(dev, "\t.addr = 0x%x\n", buf->output_pins[i].addr);
++		dev_dbg(dev, "\t.compress = %d\n",
++			buf->output_pins[i].compress);
++	}
++
++	dev_dbg(dev, "send_irq_sof = 0x%x\n", buf->send_irq_sof);
++	dev_dbg(dev, "send_irq_eof = 0x%x\n", buf->send_irq_eof);
++	dev_dbg(dev, "send_resp_sof = 0x%x\n", buf->send_resp_sof);
++	dev_dbg(dev, "send_resp_eof = 0x%x\n", buf->send_resp_eof);
++	dev_dbg(dev, "send_irq_capture_ack = 0x%x\n",
++		buf->send_irq_capture_ack);
++	dev_dbg(dev, "send_irq_capture_done = 0x%x\n",
++		buf->send_irq_capture_done);
++	dev_dbg(dev, "send_resp_capture_ack = 0x%x\n",
++		buf->send_resp_capture_ack);
++	dev_dbg(dev, "send_resp_capture_done = 0x%x\n",
++		buf->send_resp_capture_done);
++
++	dev_dbg(dev, "-----------------------------------------------------\n");
++}
+diff --git a/drivers/media/pci/intel/ipu6/ipu6-fw-isys.h b/drivers/media/pci/intel/ipu6/ipu6-fw-isys.h
+new file mode 100644
+index 000000000000..a7ffa0e22bf0
+--- /dev/null
++++ b/drivers/media/pci/intel/ipu6/ipu6-fw-isys.h
+@@ -0,0 +1,573 @@
++/* SPDX-License-Identifier: GPL-2.0-only */
++/* Copyright (C) 2013 - 2023 Intel Corporation */
++
++#ifndef IPU6_FW_ISYS_H
++#define IPU6_FW_ISYS_H
++
++#include <linux/types.h>
++
++struct device;
++struct ipu6_isys;
++
++/* Max number of Input/Output Pins */
++#define IPU6_MAX_IPINS 4
++
++#define IPU6_MAX_OPINS ((IPU6_MAX_IPINS) + 1)
++
++#define IPU6_STREAM_ID_MAX 16
++#define IPU6_NONSECURE_STREAM_ID_MAX 12
++#define IPU6_DEV_SEND_QUEUE_SIZE (IPU6_STREAM_ID_MAX)
++#define IPU6_NOF_SRAM_BLOCKS_MAX (IPU6_STREAM_ID_MAX)
++#define IPU6_N_MAX_MSG_SEND_QUEUES (IPU6_STREAM_ID_MAX)
++#define IPU6SE_STREAM_ID_MAX 8
++#define IPU6SE_NONSECURE_STREAM_ID_MAX 4
++#define IPU6SE_DEV_SEND_QUEUE_SIZE (IPU6SE_STREAM_ID_MAX)
++#define IPU6SE_NOF_SRAM_BLOCKS_MAX (IPU6SE_STREAM_ID_MAX)
++#define IPU6SE_N_MAX_MSG_SEND_QUEUES (IPU6SE_STREAM_ID_MAX)
++
++/* Single return queue for all streams/commands type */
++#define IPU6_N_MAX_MSG_RECV_QUEUES 1
++/* Single device queue for high priority commands (bypass in-order queue) */
++#define IPU6_N_MAX_DEV_SEND_QUEUES 1
++/* Single dedicated send queue for proxy interface */
++#define IPU6_N_MAX_PROXY_SEND_QUEUES 1
++/* Single dedicated recv queue for proxy interface */
++#define IPU6_N_MAX_PROXY_RECV_QUEUES 1
++/* Send queues layout */
++#define IPU6_BASE_PROXY_SEND_QUEUES 0
++#define IPU6_BASE_DEV_SEND_QUEUES \
++	(IPU6_BASE_PROXY_SEND_QUEUES + IPU6_N_MAX_PROXY_SEND_QUEUES)
++#define IPU6_BASE_MSG_SEND_QUEUES \
++	(IPU6_BASE_DEV_SEND_QUEUES + IPU6_N_MAX_DEV_SEND_QUEUES)
++/* Recv queues layout */
++#define IPU6_BASE_PROXY_RECV_QUEUES 0
++#define IPU6_BASE_MSG_RECV_QUEUES \
++	(IPU6_BASE_PROXY_RECV_QUEUES + IPU6_N_MAX_PROXY_RECV_QUEUES)
++#define IPU6_N_MAX_RECV_QUEUES \
++	(IPU6_BASE_MSG_RECV_QUEUES + IPU6_N_MAX_MSG_RECV_QUEUES)
++
++#define IPU6_N_MAX_SEND_QUEUES \
++	(IPU6_BASE_MSG_SEND_QUEUES + IPU6_N_MAX_MSG_SEND_QUEUES)
++#define IPU6SE_N_MAX_SEND_QUEUES \
++	(IPU6_BASE_MSG_SEND_QUEUES + IPU6SE_N_MAX_MSG_SEND_QUEUES)
++
++/* Max number of supported input pins routed in ISL */
++#define IPU6_MAX_IPINS_IN_ISL 2
++
++/* Max number of planes for frame formats supported by the FW */
++#define IPU6_PIN_PLANES_MAX 4
++
++#define IPU6_FW_ISYS_SENSOR_TYPE_START 14
++#define IPU6_FW_ISYS_SENSOR_TYPE_END 19
++#define IPU6SE_FW_ISYS_SENSOR_TYPE_START 6
++#define IPU6SE_FW_ISYS_SENSOR_TYPE_END 11
++/*
++ * Device close takes some time from last ack message to actual stopping
++ * of the SP processor. As long as the SP processor runs we can't proceed with
++ * clean up of resources.
++ */
++#define IPU6_ISYS_OPEN_RETRY			2000
++#define IPU6_ISYS_CLOSE_RETRY			2000
++#define IPU6_FW_CALL_TIMEOUT_JIFFIES		msecs_to_jiffies(2000)
++
++enum ipu6_fw_isys_resp_type {
++	IPU6_FW_ISYS_RESP_TYPE_STREAM_OPEN_DONE = 0,
++	IPU6_FW_ISYS_RESP_TYPE_STREAM_START_ACK,
++	IPU6_FW_ISYS_RESP_TYPE_STREAM_START_AND_CAPTURE_ACK,
++	IPU6_FW_ISYS_RESP_TYPE_STREAM_CAPTURE_ACK,
++	IPU6_FW_ISYS_RESP_TYPE_STREAM_STOP_ACK,
++	IPU6_FW_ISYS_RESP_TYPE_STREAM_FLUSH_ACK,
++	IPU6_FW_ISYS_RESP_TYPE_STREAM_CLOSE_ACK,
++	IPU6_FW_ISYS_RESP_TYPE_PIN_DATA_READY,
++	IPU6_FW_ISYS_RESP_TYPE_PIN_DATA_WATERMARK,
++	IPU6_FW_ISYS_RESP_TYPE_FRAME_SOF,
++	IPU6_FW_ISYS_RESP_TYPE_FRAME_EOF,
++	IPU6_FW_ISYS_RESP_TYPE_STREAM_START_AND_CAPTURE_DONE,
++	IPU6_FW_ISYS_RESP_TYPE_STREAM_CAPTURE_DONE,
++	IPU6_FW_ISYS_RESP_TYPE_PIN_DATA_SKIPPED,
++	IPU6_FW_ISYS_RESP_TYPE_STREAM_CAPTURE_SKIPPED,
++	IPU6_FW_ISYS_RESP_TYPE_FRAME_SOF_DISCARDED,
++	IPU6_FW_ISYS_RESP_TYPE_FRAME_EOF_DISCARDED,
++	IPU6_FW_ISYS_RESP_TYPE_STATS_DATA_READY,
++	N_IPU6_FW_ISYS_RESP_TYPE
++};
++
++enum ipu6_fw_isys_send_type {
++	IPU6_FW_ISYS_SEND_TYPE_STREAM_OPEN = 0,
++	IPU6_FW_ISYS_SEND_TYPE_STREAM_START,
++	IPU6_FW_ISYS_SEND_TYPE_STREAM_START_AND_CAPTURE,
++	IPU6_FW_ISYS_SEND_TYPE_STREAM_CAPTURE,
++	IPU6_FW_ISYS_SEND_TYPE_STREAM_STOP,
++	IPU6_FW_ISYS_SEND_TYPE_STREAM_FLUSH,
++	IPU6_FW_ISYS_SEND_TYPE_STREAM_CLOSE,
++	N_IPU6_FW_ISYS_SEND_TYPE
++};
++
++enum ipu6_fw_isys_queue_type {
++	IPU6_FW_ISYS_QUEUE_TYPE_PROXY = 0,
++	IPU6_FW_ISYS_QUEUE_TYPE_DEV,
++	IPU6_FW_ISYS_QUEUE_TYPE_MSG,
++	N_IPU6_FW_ISYS_QUEUE_TYPE
++};
++
++enum ipu6_fw_isys_stream_source {
++	IPU6_FW_ISYS_STREAM_SRC_PORT_0 = 0,
++	IPU6_FW_ISYS_STREAM_SRC_PORT_1,
++	IPU6_FW_ISYS_STREAM_SRC_PORT_2,
++	IPU6_FW_ISYS_STREAM_SRC_PORT_3,
++	IPU6_FW_ISYS_STREAM_SRC_PORT_4,
++	IPU6_FW_ISYS_STREAM_SRC_PORT_5,
++	IPU6_FW_ISYS_STREAM_SRC_PORT_6,
++	IPU6_FW_ISYS_STREAM_SRC_PORT_7,
++	IPU6_FW_ISYS_STREAM_SRC_PORT_8,
++	IPU6_FW_ISYS_STREAM_SRC_PORT_9,
++	IPU6_FW_ISYS_STREAM_SRC_PORT_10,
++	IPU6_FW_ISYS_STREAM_SRC_PORT_11,
++	IPU6_FW_ISYS_STREAM_SRC_PORT_12,
++	IPU6_FW_ISYS_STREAM_SRC_PORT_13,
++	IPU6_FW_ISYS_STREAM_SRC_PORT_14,
++	IPU6_FW_ISYS_STREAM_SRC_PORT_15,
++	IPU6_FW_ISYS_STREAM_SRC_MIPIGEN_0,
++	IPU6_FW_ISYS_STREAM_SRC_MIPIGEN_1,
++	IPU6_FW_ISYS_STREAM_SRC_MIPIGEN_2,
++	IPU6_FW_ISYS_STREAM_SRC_MIPIGEN_3,
++	IPU6_FW_ISYS_STREAM_SRC_MIPIGEN_4,
++	IPU6_FW_ISYS_STREAM_SRC_MIPIGEN_5,
++	IPU6_FW_ISYS_STREAM_SRC_MIPIGEN_6,
++	IPU6_FW_ISYS_STREAM_SRC_MIPIGEN_7,
++	IPU6_FW_ISYS_STREAM_SRC_MIPIGEN_8,
++	IPU6_FW_ISYS_STREAM_SRC_MIPIGEN_9,
++	N_IPU6_FW_ISYS_STREAM_SRC
++};
++
++#define IPU6_FW_ISYS_STREAM_SRC_CSI2_PORT0 IPU6_FW_ISYS_STREAM_SRC_PORT_0
++#define IPU6_FW_ISYS_STREAM_SRC_CSI2_PORT1 IPU6_FW_ISYS_STREAM_SRC_PORT_1
++#define IPU6_FW_ISYS_STREAM_SRC_CSI2_PORT2 IPU6_FW_ISYS_STREAM_SRC_PORT_2
++#define IPU6_FW_ISYS_STREAM_SRC_CSI2_PORT3 IPU6_FW_ISYS_STREAM_SRC_PORT_3
++
++#define IPU6_FW_ISYS_STREAM_SRC_CSI2_3PH_PORTA IPU6_FW_ISYS_STREAM_SRC_PORT_4
++#define IPU6_FW_ISYS_STREAM_SRC_CSI2_3PH_PORTB IPU6_FW_ISYS_STREAM_SRC_PORT_5
++#define IPU6_FW_ISYS_STREAM_SRC_CSI2_3PH_CPHY_PORT0 \
++	IPU6_FW_ISYS_STREAM_SRC_PORT_6
++#define IPU6_FW_ISYS_STREAM_SRC_CSI2_3PH_CPHY_PORT1 \
++	IPU6_FW_ISYS_STREAM_SRC_PORT_7
++#define IPU6_FW_ISYS_STREAM_SRC_CSI2_3PH_CPHY_PORT2 \
++	IPU6_FW_ISYS_STREAM_SRC_PORT_8
++#define IPU6_FW_ISYS_STREAM_SRC_CSI2_3PH_CPHY_PORT3 \
++	IPU6_FW_ISYS_STREAM_SRC_PORT_9
++
++#define IPU6_FW_ISYS_STREAM_SRC_MIPIGEN_PORT0 IPU6_FW_ISYS_STREAM_SRC_MIPIGEN_0
++#define IPU6_FW_ISYS_STREAM_SRC_MIPIGEN_PORT1 IPU6_FW_ISYS_STREAM_SRC_MIPIGEN_1
++
++/**
++ * enum ipu6_fw_isys_mipi_vc: MIPI csi2 spec
++ * supports up to 4 virtual per physical channel
++ */
++enum ipu6_fw_isys_mipi_vc {
++	IPU6_FW_ISYS_MIPI_VC_0 = 0,
++	IPU6_FW_ISYS_MIPI_VC_1,
++	IPU6_FW_ISYS_MIPI_VC_2,
++	IPU6_FW_ISYS_MIPI_VC_3,
++	N_IPU6_FW_ISYS_MIPI_VC
++};
++
++enum ipu6_fw_isys_frame_format_type {
++	IPU6_FW_ISYS_FRAME_FORMAT_NV11 = 0, /* 12 bit YUV 411, Y, UV plane */
++	IPU6_FW_ISYS_FRAME_FORMAT_NV12,	/* 12 bit YUV 420, Y, UV plane */
++	IPU6_FW_ISYS_FRAME_FORMAT_NV12_16, /* 16 bit YUV 420, Y, UV plane */
++	/* 12 bit YUV 420, Intel proprietary tiled format */
++	IPU6_FW_ISYS_FRAME_FORMAT_NV12_TILEY,
++
++	IPU6_FW_ISYS_FRAME_FORMAT_NV16,	/* 16 bit YUV 422, Y, UV plane */
++	IPU6_FW_ISYS_FRAME_FORMAT_NV21,	/* 12 bit YUV 420, Y, VU plane */
++	IPU6_FW_ISYS_FRAME_FORMAT_NV61,	/* 16 bit YUV 422, Y, VU plane */
++	IPU6_FW_ISYS_FRAME_FORMAT_YV12,	/* 12 bit YUV 420, Y, V, U plane */
++	IPU6_FW_ISYS_FRAME_FORMAT_YV16,	/* 16 bit YUV 422, Y, V, U plane */
++	IPU6_FW_ISYS_FRAME_FORMAT_YUV420, /* 12 bit YUV 420, Y, U, V plane */
++	IPU6_FW_ISYS_FRAME_FORMAT_YUV420_10, /* yuv420, 10 bits per subpixel */
++	IPU6_FW_ISYS_FRAME_FORMAT_YUV420_12, /* yuv420, 12 bits per subpixel */
++	IPU6_FW_ISYS_FRAME_FORMAT_YUV420_14, /* yuv420, 14 bits per subpixel */
++	IPU6_FW_ISYS_FRAME_FORMAT_YUV420_16, /* yuv420, 16 bits per subpixel */
++	IPU6_FW_ISYS_FRAME_FORMAT_YUV422, /* 16 bit YUV 422, Y, U, V plane */
++	IPU6_FW_ISYS_FRAME_FORMAT_YUV422_16, /* yuv422, 16 bits per subpixel */
++	IPU6_FW_ISYS_FRAME_FORMAT_UYVY,	/* 16 bit YUV 422, UYVY interleaved */
++	IPU6_FW_ISYS_FRAME_FORMAT_YUYV,	/* 16 bit YUV 422, YUYV interleaved */
++	IPU6_FW_ISYS_FRAME_FORMAT_YUV444, /* 24 bit YUV 444, Y, U, V plane */
++	/* Internal format, 2 y lines followed by a uvinterleaved line */
++	IPU6_FW_ISYS_FRAME_FORMAT_YUV_LINE,
++	IPU6_FW_ISYS_FRAME_FORMAT_RAW8,	/* RAW8, 1 plane */
++	IPU6_FW_ISYS_FRAME_FORMAT_RAW10, /* RAW10, 1 plane */
++	IPU6_FW_ISYS_FRAME_FORMAT_RAW12, /* RAW12, 1 plane */
++	IPU6_FW_ISYS_FRAME_FORMAT_RAW14, /* RAW14, 1 plane */
++	IPU6_FW_ISYS_FRAME_FORMAT_RAW16, /* RAW16, 1 plane */
++	/**
++	 * 16 bit RGB, 1 plane. Each 3 sub pixels are packed into one 16 bit
++	 * value, 5 bits for R, 6 bits for G and 5 bits for B.
++	 */
++	IPU6_FW_ISYS_FRAME_FORMAT_RGB565,
++	IPU6_FW_ISYS_FRAME_FORMAT_PLANAR_RGB888, /* 24 bit RGB, 3 planes */
++	IPU6_FW_ISYS_FRAME_FORMAT_RGBA888, /* 32 bit RGBA, 1 plane, A=Alpha */
++	IPU6_FW_ISYS_FRAME_FORMAT_QPLANE6, /* Internal, for advanced ISP */
++	IPU6_FW_ISYS_FRAME_FORMAT_BINARY_8, /* byte stream, used for jpeg. */
++	N_IPU6_FW_ISYS_FRAME_FORMAT
++};
++
++#define IPU6_FW_ISYS_FRAME_FORMAT_RAW	(IPU6_FW_ISYS_FRAME_FORMAT_RAW16)
++
++enum ipu6_fw_isys_pin_type {
++	/* captured as MIPI packets */
++	IPU6_FW_ISYS_PIN_TYPE_MIPI = 0,
++	/* captured through the SoC path */
++	IPU6_FW_ISYS_PIN_TYPE_RAW_SOC = 3,
++};
++
++/**
++ * enum ipu6_fw_isys_mipi_store_mode. Describes if long MIPI packets reach
++ * MIPI SRAM with the long packet header or
++ * if not, then only option is to capture it with pin type MIPI.
++ */
++enum ipu6_fw_isys_mipi_store_mode {
++	IPU6_FW_ISYS_MIPI_STORE_MODE_NORMAL = 0,
++	IPU6_FW_ISYS_MIPI_STORE_MODE_DISCARD_LONG_HEADER,
++	N_IPU6_FW_ISYS_MIPI_STORE_MODE
++};
++
++enum ipu6_fw_isys_capture_mode {
++	IPU6_FW_ISYS_CAPTURE_MODE_REGULAR = 0,
++	IPU6_FW_ISYS_CAPTURE_MODE_BURST,
++	N_IPU6_FW_ISYS_CAPTURE_MODE,
++};
++
++enum ipu6_fw_isys_sensor_mode {
++	IPU6_FW_ISYS_SENSOR_MODE_NORMAL = 0,
++	IPU6_FW_ISYS_SENSOR_MODE_TOBII,
++	N_IPU6_FW_ISYS_SENSOR_MODE,
++};
++
++enum ipu6_fw_isys_error {
++	IPU6_FW_ISYS_ERROR_NONE = 0,
++	IPU6_FW_ISYS_ERROR_FW_INTERNAL_CONSISTENCY,
++	IPU6_FW_ISYS_ERROR_HW_CONSISTENCY,
++	IPU6_FW_ISYS_ERROR_DRIVER_INVALID_COMMAND_SEQUENCE,
++	IPU6_FW_ISYS_ERROR_DRIVER_INVALID_DEVICE_CONFIGURATION,
++	IPU6_FW_ISYS_ERROR_DRIVER_INVALID_STREAM_CONFIGURATION,
++	IPU6_FW_ISYS_ERROR_DRIVER_INVALID_FRAME_CONFIGURATION,
++	IPU6_FW_ISYS_ERROR_INSUFFICIENT_RESOURCES,
++	IPU6_FW_ISYS_ERROR_HW_REPORTED_STR2MMIO,
++	IPU6_FW_ISYS_ERROR_HW_REPORTED_SIG2CIO,
++	IPU6_FW_ISYS_ERROR_SENSOR_FW_SYNC,
++	IPU6_FW_ISYS_ERROR_STREAM_IN_SUSPENSION,
++	IPU6_FW_ISYS_ERROR_RESPONSE_QUEUE_FULL,
++	N_IPU6_FW_ISYS_ERROR
++};
++
++enum ipu6_fw_proxy_error {
++	IPU6_FW_PROXY_ERROR_NONE = 0,
++	IPU6_FW_PROXY_ERROR_INVALID_WRITE_REGION,
++	IPU6_FW_PROXY_ERROR_INVALID_WRITE_OFFSET,
++	N_IPU6_FW_PROXY_ERROR
++};
++
++/* firmware ABI structure below are aligned in firmware, no need pack */
++struct ipu6_fw_isys_buffer_partition_abi {
++	u32 num_gda_pages[IPU6_STREAM_ID_MAX];
++};
++
++struct ipu6_fw_isys_fw_config {
++	struct ipu6_fw_isys_buffer_partition_abi buffer_partition;
++	u32 num_send_queues[N_IPU6_FW_ISYS_QUEUE_TYPE];
++	u32 num_recv_queues[N_IPU6_FW_ISYS_QUEUE_TYPE];
++};
++
++/**
++ * struct ipu6_fw_isys_resolution_abi: Generic resolution structure.
++ */
++struct ipu6_fw_isys_resolution_abi {
++	u32 width;
++	u32 height;
++};
++
++/**
++ * struct ipu6_fw_isys_output_pin_payload_abi
++ * @out_buf_id: Points to output pin buffer - buffer identifier
++ * @addr: Points to output pin buffer - CSS Virtual Address
++ * @compress: Request frame compression (1), or  not (0)
++ */
++struct ipu6_fw_isys_output_pin_payload_abi {
++	u64 out_buf_id;
++	u32 addr;
++	u32 compress;
++};
++
++/**
++ * struct ipu6_fw_isys_output_pin_info_abi
++ * @output_res: output pin resolution
++ * @stride: output stride in Bytes (not valid for statistics)
++ * @watermark_in_lines: pin watermark level in lines
++ * @payload_buf_size: minimum size in Bytes of all buffers that will be
++ *			supplied for capture on this pin
++ * @send_irq: assert if pin event should trigger irq
++ * @pt: pin type -real format "enum ipu6_fw_isys_pin_type"
++ * @ft: frame format type -real format "enum ipu6_fw_isys_frame_format_type"
++ * @input_pin_id: related input pin id
++ * @reserve_compression: reserve compression resources for pin
++ */
++struct ipu6_fw_isys_output_pin_info_abi {
++	struct ipu6_fw_isys_resolution_abi output_res;
++	u32 stride;
++	u32 watermark_in_lines;
++	u32 payload_buf_size;
++	u32 ts_offsets[IPU6_PIN_PLANES_MAX];
++	u32 s2m_pixel_soc_pixel_remapping;
++	u32 csi_be_soc_pixel_remapping;
++	u8 send_irq;
++	u8 input_pin_id;
++	u8 pt;
++	u8 ft;
++	u8 reserved;
++	u8 reserve_compression;
++	u8 snoopable;
++	u8 error_handling_enable;
++	u32 sensor_type;
++};
++
++/**
++ * struct ipu6_fw_isys_input_pin_info_abi
++ * @input_res: input resolution
++ * @dt: mipi data type ((enum ipu6_fw_isys_mipi_data_type)
++ * @mipi_store_mode: defines if legacy long packet header will be stored or
++ *		     discarded if discarded, output pin type for this
++ *		     input pin can only be MIPI
++ *		     (enum ipu6_fw_isys_mipi_store_mode)
++ * @bits_per_pix: native bits per pixel
++ * @mapped_dt: actual data type from sensor
++ * @mipi_decompression: defines which compression will be in mipi backend
++ * @crop_first_and_last_lines    Control whether to crop the
++ *                              first and last line of the
++ *                              input image. Crop done by HW
++ *                              device.
++ * @capture_mode: mode of capture, regular or burst, default value is regular
++ */
++struct ipu6_fw_isys_input_pin_info_abi {
++	struct ipu6_fw_isys_resolution_abi input_res;
++	u8 dt;
++	u8 mipi_store_mode;
++	u8 bits_per_pix;
++	u8 mapped_dt;
++	u8 mipi_decompression;
++	u8 crop_first_and_last_lines;
++	u8 capture_mode;
++	u8 reserved;
++};
++
++/**
++ * struct ipu6_fw_isys_cropping_abi - cropping coordinates
++ */
++struct ipu6_fw_isys_cropping_abi {
++	s32 top_offset;
++	s32 left_offset;
++	s32 bottom_offset;
++	s32 right_offset;
++};
++
++/**
++ * struct ipu6_fw_isys_stream_cfg_data_abi
++ * ISYS stream configuration data structure
++ * @crop: for extended use and is not used in FW currently
++ * @input_pins: input pin descriptors
++ * @output_pins: output pin descriptors
++ * @compfmt: de-compression setting for User Defined Data
++ * @nof_input_pins: number of input pins
++ * @nof_output_pins: number of output pins
++ * @send_irq_sof_discarded: send irq on discarded frame sof response
++ *		- if '1' it will override the send_resp_sof_discarded
++ *		  and send the response
++ *		- if '0' the send_resp_sof_discarded will determine
++ *		  whether to send the response
++ * @send_irq_eof_discarded: send irq on discarded frame eof response
++ *		- if '1' it will override the send_resp_eof_discarded
++ *		  and send the response
++ *		- if '0' the send_resp_eof_discarded will determine
++ *		  whether to send the response
++ * @send_resp_sof_discarded: send response for discarded frame sof detected,
++ *			     used only when send_irq_sof_discarded is '0'
++ * @send_resp_eof_discarded: send response for discarded frame eof detected,
++ *			     used only when send_irq_eof_discarded is '0'
++ * @src: Stream source index e.g. MIPI_generator_0, CSI2-rx_1
++ * @vc: MIPI Virtual Channel (up to 4 virtual per physical channel)
++ * @isl_use: indicates whether stream requires ISL and how
++ * @sensor_type: type of connected sensor, tobii or others, default is 0
++ */
++struct ipu6_fw_isys_stream_cfg_data_abi {
++	struct ipu6_fw_isys_cropping_abi crop;
++	struct ipu6_fw_isys_input_pin_info_abi input_pins[IPU6_MAX_IPINS];
++	struct ipu6_fw_isys_output_pin_info_abi output_pins[IPU6_MAX_OPINS];
++	u32 compfmt;
++	u8 nof_input_pins;
++	u8 nof_output_pins;
++	u8 send_irq_sof_discarded;
++	u8 send_irq_eof_discarded;
++	u8 send_resp_sof_discarded;
++	u8 send_resp_eof_discarded;
++	u8 src;
++	u8 vc;
++	u8 isl_use;
++	u8 sensor_type;
++	u8 reserved;
++	u8 reserved2;
++};
++
++/**
++ * struct ipu6_fw_isys_frame_buff_set - frame buffer set
++ * @output_pins: output pin addresses
++ * @send_irq_sof: send irq on frame sof response
++ *		- if '1' it will override the send_resp_sof and
++ *		  send the response
++ *		- if '0' the send_resp_sof will determine whether to
++ *		  send the response
++ * @send_irq_eof: send irq on frame eof response
++ *		- if '1' it will override the send_resp_eof and
++ *		  send the response
++ *		- if '0' the send_resp_eof will determine whether to
++ *		  send the response
++ * @send_resp_sof: send response for frame sof detected,
++ *		   used only when send_irq_sof is '0'
++ * @send_resp_eof: send response for frame eof detected,
++ *		   used only when send_irq_eof is '0'
++ * @send_resp_capture_ack: send response for capture ack event
++ * @send_resp_capture_done: send response for capture done event
++ */
++struct ipu6_fw_isys_frame_buff_set_abi {
++	struct ipu6_fw_isys_output_pin_payload_abi output_pins[IPU6_MAX_OPINS];
++	u8 send_irq_sof;
++	u8 send_irq_eof;
++	u8 send_irq_capture_ack;
++	u8 send_irq_capture_done;
++	u8 send_resp_sof;
++	u8 send_resp_eof;
++	u8 send_resp_capture_ack;
++	u8 send_resp_capture_done;
++	u8 reserved[8];
++};
++
++/**
++ * struct ipu6_fw_isys_error_info_abi
++ * @error: error code if something went wrong
++ * @error_details: depending on error code, it may contain additional error info
++ */
++struct ipu6_fw_isys_error_info_abi {
++	u32 error;
++	u32 error_details;
++};
++
++/**
++ * struct ipu6_fw_isys_resp_info_comm
++ * @pin: this var is only valid for pin event related responses,
++ *     contains pin addresses
++ * @error_info: error information from the FW
++ * @timestamp: Time information for event if available
++ * @stream_handle: stream id the response corresponds to
++ * @type: response type (enum ipu6_fw_isys_resp_type)
++ * @pin_id: pin id that the pin payload corresponds to
++ */
++struct ipu6_fw_isys_resp_info_abi {
++	u64 buf_id;
++	struct ipu6_fw_isys_output_pin_payload_abi pin;
++	struct ipu6_fw_isys_error_info_abi error_info;
++	u32 timestamp[2];
++	u8 stream_handle;
++	u8 type;
++	u8 pin_id;
++	u8 reserved;
++	u32 reserved2;
++};
++
++/**
++ * struct ipu6_fw_isys_proxy_error_info_comm
++ * @proxy_error: error code if something went wrong
++ * @proxy_error_details: depending on error code, it may contain additional
++ *			error info
++ */
++struct ipu6_fw_isys_proxy_error_info_abi {
++	u32 error;
++	u32 error_details;
++};
++
++struct ipu6_fw_isys_proxy_resp_info_abi {
++	u32 request_id;
++	struct ipu6_fw_isys_proxy_error_info_abi error_info;
++};
++
++/**
++ * struct ipu6_fw_proxy_write_queue_token
++ * @request_id: update id for the specific proxy write request
++ * @region_index: Region id for the proxy write request
++ * @offset: Offset of the write request according to the base address
++ *	    of the region
++ * @value: Value that is requested to be written with the proxy write request
++ */
++struct ipu6_fw_proxy_write_queue_token {
++	u32 request_id;
++	u32 region_index;
++	u32 offset;
++	u32 value;
++};
++
++/**
++ * struct ipu6_fw_resp_queue_token
++ */
++struct ipu6_fw_resp_queue_token {
++	struct ipu6_fw_isys_resp_info_abi resp_info;
++};
++
++/**
++ * struct ipu6_fw_send_queue_token
++ */
++struct ipu6_fw_send_queue_token {
++	u64 buf_handle;
++	u32 payload;
++	u16 send_type;
++	u16 stream_id;
++};
++
++/**
++ * struct ipu6_fw_proxy_resp_queue_token
++ */
++struct ipu6_fw_proxy_resp_queue_token {
++	struct ipu6_fw_isys_proxy_resp_info_abi proxy_resp_info;
++};
++
++/**
++ * struct ipu6_fw_proxy_send_queue_token
++ */
++struct ipu6_fw_proxy_send_queue_token {
++	u32 request_id;
++	u32 region_index;
++	u32 offset;
++	u32 value;
++};
++
++void
++ipu6_fw_isys_dump_stream_cfg(struct device *dev,
++			     struct ipu6_fw_isys_stream_cfg_data_abi *cfg);
++void
++ipu6_fw_isys_dump_frame_buff_set(struct device *dev,
++				 struct ipu6_fw_isys_frame_buff_set_abi *buf,
++				 unsigned int outputs);
++int ipu6_fw_isys_init(struct ipu6_isys *isys, unsigned int num_streams);
++int ipu6_fw_isys_close(struct ipu6_isys *isys);
++int ipu6_fw_isys_simple_cmd(struct ipu6_isys *isys,
++			    const unsigned int stream_handle, u16 send_type);
++int ipu6_fw_isys_complex_cmd(struct ipu6_isys *isys,
++			     const unsigned int stream_handle,
++			     void *cpu_mapped_buf, dma_addr_t dma_mapped_buf,
++			     size_t size, u16 send_type);
++int ipu6_fw_isys_send_proxy_token(struct ipu6_isys *isys,
++				  unsigned int req_id,
++				  unsigned int index,
++				  unsigned int offset, u32 value);
++void ipu6_fw_isys_cleanup(struct ipu6_isys *isys);
++struct ipu6_fw_isys_resp_info_abi *
++ipu6_fw_isys_get_resp(void *context, unsigned int queue);
++void ipu6_fw_isys_put_resp(void *context, unsigned int queue);
++#endif
+-- 
+2.43.2
+
+
+From e69a245fa4db99e5983170aab73f962dc99d3149 Mon Sep 17 00:00:00 2001
+From: Bingbu Cao <bingbu.cao@intel.com>
+Date: Thu, 11 Jan 2024 14:55:22 +0800
+Subject: [PATCH 15/33] media: intel/ipu6: add IPU6 CSI2 receiver v4l2
+ sub-device
+
+Input system CSI2 receiver is exposed as a v4l2 sub-device.
+Each CSI2 sub-device represent one single CSI2 hardware port
+which be linked with external sub-device such camera sensor
+by linked with ISYS CSI2's sink pad. CSI2 source pad is linked
+to the sink pad of video capture device.
+
+Signed-off-by: Bingbu Cao <bingbu.cao@intel.com>
+---
+ drivers/media/pci/intel/ipu6/ipu6-isys-csi2.c | 666 ++++++++++++++++++
+ drivers/media/pci/intel/ipu6/ipu6-isys-csi2.h |  81 +++
+ .../media/pci/intel/ipu6/ipu6-isys-subdev.c   | 381 ++++++++++
+ .../media/pci/intel/ipu6/ipu6-isys-subdev.h   |  61 ++
+ .../intel/ipu6/ipu6-platform-isys-csi2-reg.h  | 189 +++++
+ 5 files changed, 1378 insertions(+)
+ create mode 100644 drivers/media/pci/intel/ipu6/ipu6-isys-csi2.c
+ create mode 100644 drivers/media/pci/intel/ipu6/ipu6-isys-csi2.h
+ create mode 100644 drivers/media/pci/intel/ipu6/ipu6-isys-subdev.c
+ create mode 100644 drivers/media/pci/intel/ipu6/ipu6-isys-subdev.h
+ create mode 100644 drivers/media/pci/intel/ipu6/ipu6-platform-isys-csi2-reg.h
+
+diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys-csi2.c b/drivers/media/pci/intel/ipu6/ipu6-isys-csi2.c
+new file mode 100644
+index 000000000000..ac9fa3e0d7ab
+--- /dev/null
++++ b/drivers/media/pci/intel/ipu6/ipu6-isys-csi2.c
+@@ -0,0 +1,666 @@
++// SPDX-License-Identifier: GPL-2.0-only
++/*
++ * Copyright (C) 2013 - 2023 Intel Corporation
++ */
++
++#include <linux/atomic.h>
++#include <linux/bitfield.h>
++#include <linux/bits.h>
++#include <linux/delay.h>
++#include <linux/device.h>
++#include <linux/err.h>
++#include <linux/io.h>
++#include <linux/minmax.h>
++#include <linux/sprintf.h>
++
++#include <media/media-entity.h>
++#include <media/v4l2-ctrls.h>
++#include <media/v4l2-device.h>
++#include <media/v4l2-event.h>
++#include <media/v4l2-subdev.h>
++
++#include "ipu6-bus.h"
++#include "ipu6-isys.h"
++#include "ipu6-isys-csi2.h"
++#include "ipu6-isys-subdev.h"
++#include "ipu6-platform-isys-csi2-reg.h"
++
++static const u32 csi2_supported_codes[] = {
++	MEDIA_BUS_FMT_RGB565_1X16,
++	MEDIA_BUS_FMT_RGB888_1X24,
++	MEDIA_BUS_FMT_UYVY8_1X16,
++	MEDIA_BUS_FMT_YUYV8_1X16,
++	MEDIA_BUS_FMT_SBGGR10_1X10,
++	MEDIA_BUS_FMT_SGBRG10_1X10,
++	MEDIA_BUS_FMT_SGRBG10_1X10,
++	MEDIA_BUS_FMT_SRGGB10_1X10,
++	MEDIA_BUS_FMT_SBGGR12_1X12,
++	MEDIA_BUS_FMT_SGBRG12_1X12,
++	MEDIA_BUS_FMT_SGRBG12_1X12,
++	MEDIA_BUS_FMT_SRGGB12_1X12,
++	MEDIA_BUS_FMT_SBGGR8_1X8,
++	MEDIA_BUS_FMT_SGBRG8_1X8,
++	MEDIA_BUS_FMT_SGRBG8_1X8,
++	MEDIA_BUS_FMT_SRGGB8_1X8,
++	0
++};
++
++/*
++ * Strings corresponding to CSI-2 receiver errors are here.
++ * Corresponding macros are defined in the header file.
++ */
++static const struct ipu6_csi2_error dphy_rx_errors[] = {
++	{ "Single packet header error corrected", true },
++	{ "Multiple packet header errors detected", true },
++	{ "Payload checksum (CRC) error", true },
++	{ "Transfer FIFO overflow", false },
++	{ "Reserved short packet data type detected", true },
++	{ "Reserved long packet data type detected", true },
++	{ "Incomplete long packet detected", false },
++	{ "Frame sync error", false },
++	{ "Line sync error", false },
++	{ "DPHY recoverable synchronization error", true },
++	{ "DPHY fatal error", false },
++	{ "DPHY elastic FIFO overflow", false },
++	{ "Inter-frame short packet discarded", true },
++	{ "Inter-frame long packet discarded", true },
++	{ "MIPI pktgen overflow", false },
++	{ "MIPI pktgen data loss", false },
++	{ "FIFO overflow", false },
++	{ "Lane deskew", false },
++	{ "SOT sync error", false },
++	{ "HSIDLE detected", false }
++};
++
++s64 ipu6_isys_csi2_get_link_freq(struct ipu6_isys_csi2 *csi2)
++{
++	struct media_pad *src_pad;
++	struct v4l2_subdev *ext_sd;
++	struct device *dev;
++
++	if (!csi2)
++		return -EINVAL;
++
++	dev = &csi2->isys->adev->auxdev.dev;
++	src_pad = media_entity_remote_source_pad_unique(&csi2->asd.sd.entity);
++	if (IS_ERR_OR_NULL(src_pad)) {
++		dev_err(dev, "can't get source pad of %s\n", csi2->asd.sd.name);
++		return -ENOLINK;
++	}
++
++	ext_sd = media_entity_to_v4l2_subdev(src_pad->entity);
++	if (WARN(!ext_sd, "Failed to get subdev for %s\n", csi2->asd.sd.name))
++		return -ENODEV;
++
++	return v4l2_get_link_freq(ext_sd->ctrl_handler, 0, 0);
++}
++
++static int csi2_subscribe_event(struct v4l2_subdev *sd, struct v4l2_fh *fh,
++				struct v4l2_event_subscription *sub)
++{
++	struct ipu6_isys_subdev *asd = to_ipu6_isys_subdev(sd);
++	struct ipu6_isys_csi2 *csi2 = to_ipu6_isys_csi2(asd);
++	struct device *dev = &csi2->isys->adev->auxdev.dev;
++
++	dev_dbg(dev, "csi2 subscribe event(type %u id %u)\n",
++		sub->type, sub->id);
++
++	switch (sub->type) {
++	case V4L2_EVENT_FRAME_SYNC:
++		return v4l2_event_subscribe(fh, sub, 10, NULL);
++	case V4L2_EVENT_CTRL:
++		return v4l2_ctrl_subscribe_event(fh, sub);
++	default:
++		return -EINVAL;
++	}
++}
++
++static const struct v4l2_subdev_core_ops csi2_sd_core_ops = {
++	.subscribe_event = csi2_subscribe_event,
++	.unsubscribe_event = v4l2_event_subdev_unsubscribe,
++};
++
++/*
++ * The input system CSI2+ receiver has several
++ * parameters affecting the receiver timings. These depend
++ * on the MIPI bus frequency F in Hz (sensor transmitter rate)
++ * as follows:
++ *	register value = (A/1e9 + B * UI) / COUNT_ACC
++ * where
++ *	UI = 1 / (2 * F) in seconds
++ *	COUNT_ACC = counter accuracy in seconds
++ *	COUNT_ACC = 0.125 ns = 1 / 8 ns, ACCINV = 8.
++ *
++ * A and B are coefficients from the table below,
++ * depending whether the register minimum or maximum value is
++ * calculated.
++ *				       Minimum     Maximum
++ * Clock lane			       A     B     A     B
++ * reg_rx_csi_dly_cnt_termen_clane     0     0    38     0
++ * reg_rx_csi_dly_cnt_settle_clane    95    -8   300   -16
++ * Data lanes
++ * reg_rx_csi_dly_cnt_termen_dlane0    0     0    35     4
++ * reg_rx_csi_dly_cnt_settle_dlane0   85    -2   145    -6
++ * reg_rx_csi_dly_cnt_termen_dlane1    0     0    35     4
++ * reg_rx_csi_dly_cnt_settle_dlane1   85    -2   145    -6
++ * reg_rx_csi_dly_cnt_termen_dlane2    0     0    35     4
++ * reg_rx_csi_dly_cnt_settle_dlane2   85    -2   145    -6
++ * reg_rx_csi_dly_cnt_termen_dlane3    0     0    35     4
++ * reg_rx_csi_dly_cnt_settle_dlane3   85    -2   145    -6
++ *
++ * We use the minimum values of both A and B.
++ */
++
++#define DIV_SHIFT	8
++#define CSI2_ACCINV	8
++
++static u32 calc_timing(s32 a, s32 b, s64 link_freq, s32 accinv)
++{
++	return accinv * a + (accinv * b * (500000000 >> DIV_SHIFT)
++			     / (s32)(link_freq >> DIV_SHIFT));
++}
++
++static int
++ipu6_isys_csi2_calc_timing(struct ipu6_isys_csi2 *csi2,
++			   struct ipu6_isys_csi2_timing *timing, s32 accinv)
++{
++	struct device *dev = &csi2->isys->adev->auxdev.dev;
++	s64 link_freq;
++
++	link_freq = ipu6_isys_csi2_get_link_freq(csi2);
++	if (link_freq < 0)
++		return link_freq;
++
++	timing->ctermen = calc_timing(CSI2_CSI_RX_DLY_CNT_TERMEN_CLANE_A,
++				      CSI2_CSI_RX_DLY_CNT_TERMEN_CLANE_B,
++				      link_freq, accinv);
++	timing->csettle = calc_timing(CSI2_CSI_RX_DLY_CNT_SETTLE_CLANE_A,
++				      CSI2_CSI_RX_DLY_CNT_SETTLE_CLANE_B,
++				      link_freq, accinv);
++	timing->dtermen = calc_timing(CSI2_CSI_RX_DLY_CNT_TERMEN_DLANE_A,
++				      CSI2_CSI_RX_DLY_CNT_TERMEN_DLANE_B,
++				      link_freq, accinv);
++	timing->dsettle = calc_timing(CSI2_CSI_RX_DLY_CNT_SETTLE_DLANE_A,
++				      CSI2_CSI_RX_DLY_CNT_SETTLE_DLANE_B,
++				      link_freq, accinv);
++
++	dev_dbg(dev, "ctermen %u csettle %u dtermen %u dsettle %u\n",
++		timing->ctermen, timing->csettle,
++		timing->dtermen, timing->dsettle);
++
++	return 0;
++}
++
++void ipu6_isys_register_errors(struct ipu6_isys_csi2 *csi2)
++{
++	u32 irq = readl(csi2->base + CSI_PORT_REG_BASE_IRQ_CSI +
++			CSI_PORT_REG_BASE_IRQ_STATUS_OFFSET);
++	struct ipu6_isys *isys = csi2->isys;
++	u32 mask;
++
++	mask = isys->pdata->ipdata->csi2.irq_mask;
++	writel(irq & mask, csi2->base + CSI_PORT_REG_BASE_IRQ_CSI +
++	       CSI_PORT_REG_BASE_IRQ_CLEAR_OFFSET);
++	csi2->receiver_errors |= irq & mask;
++}
++
++void ipu6_isys_csi2_error(struct ipu6_isys_csi2 *csi2)
++{
++	struct device *dev = &csi2->isys->adev->auxdev.dev;
++	const struct ipu6_csi2_error *errors;
++	u32 status;
++	u32 i;
++
++	/* register errors once more in case of interrupts are disabled */
++	ipu6_isys_register_errors(csi2);
++	status = csi2->receiver_errors;
++	csi2->receiver_errors = 0;
++	errors = dphy_rx_errors;
++
++	for (i = 0; i < CSI_RX_NUM_ERRORS_IN_IRQ; i++) {
++		if (status & BIT(i))
++			dev_err_ratelimited(dev, "csi2-%i error: %s\n",
++					    csi2->port, errors[i].error_string);
++	}
++}
++
++static int ipu6_isys_csi2_set_stream(struct v4l2_subdev *sd,
++				     const struct ipu6_isys_csi2_timing *timing,
++				     unsigned int nlanes, int enable)
++{
++	struct ipu6_isys_subdev *asd = to_ipu6_isys_subdev(sd);
++	struct ipu6_isys_csi2 *csi2 = to_ipu6_isys_csi2(asd);
++	struct ipu6_isys *isys = csi2->isys;
++	struct device *dev = &isys->adev->auxdev.dev;
++	struct ipu6_isys_csi2_config cfg;
++	unsigned int nports;
++	int ret = 0;
++	u32 mask = 0;
++	u32 i;
++
++	dev_dbg(dev, "stream %s CSI2-%u with %u lanes\n", enable ? "on" : "off",
++		csi2->port, nlanes);
++
++	cfg.port = csi2->port;
++	cfg.nlanes = nlanes;
++
++	mask = isys->pdata->ipdata->csi2.irq_mask;
++	nports = isys->pdata->ipdata->csi2.nports;
++
++	if (!enable) {
++		writel(0, csi2->base + CSI_REG_CSI_FE_ENABLE);
++		writel(0, csi2->base + CSI_REG_PPI2CSI_ENABLE);
++
++		writel(0,
++		       csi2->base + CSI_PORT_REG_BASE_IRQ_CSI +
++		       CSI_PORT_REG_BASE_IRQ_ENABLE_OFFSET);
++		writel(mask,
++		       csi2->base + CSI_PORT_REG_BASE_IRQ_CSI +
++		       CSI_PORT_REG_BASE_IRQ_CLEAR_OFFSET);
++		writel(0,
++		       csi2->base + CSI_PORT_REG_BASE_IRQ_CSI_SYNC +
++		       CSI_PORT_REG_BASE_IRQ_ENABLE_OFFSET);
++		writel(0xffffffff,
++		       csi2->base + CSI_PORT_REG_BASE_IRQ_CSI_SYNC +
++		       CSI_PORT_REG_BASE_IRQ_CLEAR_OFFSET);
++
++		isys->phy_set_power(isys, &cfg, timing, false);
++
++		writel(0, isys->pdata->base + CSI_REG_HUB_FW_ACCESS_PORT
++		       (isys->pdata->ipdata->csi2.fw_access_port_ofs,
++			csi2->port));
++		writel(0, isys->pdata->base +
++		       CSI_REG_HUB_DRV_ACCESS_PORT(csi2->port));
++
++		return ret;
++	}
++
++	/* reset port reset */
++	writel(0x1, csi2->base + CSI_REG_PORT_GPREG_SRST);
++	usleep_range(100, 200);
++	writel(0x0, csi2->base + CSI_REG_PORT_GPREG_SRST);
++
++	/* enable port clock */
++	for (i = 0; i < nports; i++) {
++		writel(1, isys->pdata->base + CSI_REG_HUB_DRV_ACCESS_PORT(i));
++		writel(1, isys->pdata->base + CSI_REG_HUB_FW_ACCESS_PORT
++		       (isys->pdata->ipdata->csi2.fw_access_port_ofs, i));
++	}
++
++	/* enable all error related irq */
++	writel(mask,
++	       csi2->base + CSI_PORT_REG_BASE_IRQ_CSI +
++	       CSI_PORT_REG_BASE_IRQ_STATUS_OFFSET);
++	writel(mask,
++	       csi2->base + CSI_PORT_REG_BASE_IRQ_CSI +
++	       CSI_PORT_REG_BASE_IRQ_MASK_OFFSET);
++	writel(mask,
++	       csi2->base + CSI_PORT_REG_BASE_IRQ_CSI +
++	       CSI_PORT_REG_BASE_IRQ_CLEAR_OFFSET);
++	writel(mask,
++	       csi2->base + CSI_PORT_REG_BASE_IRQ_CSI +
++	       CSI_PORT_REG_BASE_IRQ_LEVEL_NOT_PULSE_OFFSET);
++	writel(mask,
++	       csi2->base + CSI_PORT_REG_BASE_IRQ_CSI +
++	       CSI_PORT_REG_BASE_IRQ_ENABLE_OFFSET);
++
++	/*
++	 * Using event from firmware instead of irq to handle CSI2 sync event
++	 * which can reduce system wakeups. If CSI2 sync irq enabled, we need
++	 * disable the firmware CSI2 sync event to avoid duplicate handling.
++	 */
++	writel(0xffffffff, csi2->base + CSI_PORT_REG_BASE_IRQ_CSI_SYNC +
++	       CSI_PORT_REG_BASE_IRQ_STATUS_OFFSET);
++	writel(0, csi2->base + CSI_PORT_REG_BASE_IRQ_CSI_SYNC +
++	       CSI_PORT_REG_BASE_IRQ_MASK_OFFSET);
++	writel(0xffffffff, csi2->base + CSI_PORT_REG_BASE_IRQ_CSI_SYNC +
++	       CSI_PORT_REG_BASE_IRQ_CLEAR_OFFSET);
++	writel(0, csi2->base + CSI_PORT_REG_BASE_IRQ_CSI_SYNC +
++	       CSI_PORT_REG_BASE_IRQ_LEVEL_NOT_PULSE_OFFSET);
++	writel(0xffffffff, csi2->base + CSI_PORT_REG_BASE_IRQ_CSI_SYNC +
++	       CSI_PORT_REG_BASE_IRQ_ENABLE_OFFSET);
++
++	/* configure to enable FE and PPI2CSI */
++	writel(0, csi2->base + CSI_REG_CSI_FE_MODE);
++	writel(CSI_SENSOR_INPUT, csi2->base + CSI_REG_CSI_FE_MUX_CTRL);
++	writel(CSI_CNTR_SENSOR_LINE_ID | CSI_CNTR_SENSOR_FRAME_ID,
++	       csi2->base + CSI_REG_CSI_FE_SYNC_CNTR_SEL);
++	writel(FIELD_PREP(PPI_INTF_CONFIG_NOF_ENABLED_DLANES_MASK, nlanes - 1),
++	       csi2->base + CSI_REG_PPI2CSI_CONFIG_PPI_INTF);
++
++	writel(1, csi2->base + CSI_REG_PPI2CSI_ENABLE);
++	writel(1, csi2->base + CSI_REG_CSI_FE_ENABLE);
++
++	ret = isys->phy_set_power(isys, &cfg, timing, true);
++	if (ret)
++		dev_err(dev, "csi-%d phy power up failed %d\n", csi2->port,
++			ret);
++
++	return ret;
++}
++
++static int set_stream(struct v4l2_subdev *sd, int enable)
++{
++	struct ipu6_isys_subdev *asd = to_ipu6_isys_subdev(sd);
++	struct ipu6_isys_csi2 *csi2 = to_ipu6_isys_csi2(asd);
++	struct device *dev = &csi2->isys->adev->auxdev.dev;
++	struct ipu6_isys_csi2_timing timing = { };
++	unsigned int nlanes;
++	int ret;
++
++	dev_dbg(dev, "csi2 stream %s callback\n", enable ? "on" : "off");
++
++	if (!enable) {
++		csi2->stream_count--;
++		if (csi2->stream_count)
++			return 0;
++
++		ipu6_isys_csi2_set_stream(sd, &timing, 0, enable);
++		return 0;
++	}
++
++	if (csi2->stream_count) {
++		csi2->stream_count++;
++		return 0;
++	}
++
++	nlanes = csi2->nlanes;
++
++	ret = ipu6_isys_csi2_calc_timing(csi2, &timing, CSI2_ACCINV);
++	if (ret)
++		return ret;
++
++	ret = ipu6_isys_csi2_set_stream(sd, &timing, nlanes, enable);
++	if (ret)
++		return ret;
++
++	csi2->stream_count++;
++
++	return 0;
++}
++
++static int ipu6_isys_csi2_set_sel(struct v4l2_subdev *sd,
++				  struct v4l2_subdev_state *state,
++				  struct v4l2_subdev_selection *sel)
++{
++	struct ipu6_isys_subdev *asd = to_ipu6_isys_subdev(sd);
++	struct device *dev = &asd->isys->adev->auxdev.dev;
++	struct v4l2_mbus_framefmt *sink_ffmt;
++	struct v4l2_mbus_framefmt *src_ffmt;
++	struct v4l2_rect *crop;
++
++	if (sel->pad == CSI2_PAD_SINK || sel->target != V4L2_SEL_TGT_CROP)
++		return -EINVAL;
++
++	sink_ffmt = v4l2_subdev_state_get_opposite_stream_format(state,
++								 sel->pad,
++								 sel->stream);
++	if (!sink_ffmt)
++		return -EINVAL;
++
++	src_ffmt = v4l2_subdev_state_get_stream_format(state, sel->pad,
++						       sel->stream);
++	if (!src_ffmt)
++		return -EINVAL;
++
++	crop = v4l2_subdev_state_get_stream_crop(state, sel->pad, sel->stream);
++	if (!crop)
++		return -EINVAL;
++
++	/* Only vertical cropping is supported */
++	sel->r.left = 0;
++	sel->r.width = sink_ffmt->width;
++	/* Non-bayer formats can't be single line cropped */
++	if (!ipu6_isys_is_bayer_format(sink_ffmt->code))
++		sel->r.top &= ~1;
++	sel->r.height = clamp(sel->r.height & ~1, IPU6_ISYS_MIN_HEIGHT,
++			      sink_ffmt->height - sel->r.top);
++	*crop = sel->r;
++
++	/* update source pad format */
++	src_ffmt->width = sel->r.width;
++	src_ffmt->height = sel->r.height;
++	if (ipu6_isys_is_bayer_format(sink_ffmt->code))
++		src_ffmt->code = ipu6_isys_convert_bayer_order(sink_ffmt->code,
++							       sel->r.left,
++							       sel->r.top);
++	dev_dbg(dev, "set crop for %s sel: %d,%d,%d,%d code: 0x%x\n",
++		sd->name, sel->r.left, sel->r.top, sel->r.width, sel->r.height,
++		src_ffmt->code);
++
++	return 0;
++}
++
++static int ipu6_isys_csi2_get_sel(struct v4l2_subdev *sd,
++				  struct v4l2_subdev_state *state,
++				  struct v4l2_subdev_selection *sel)
++{
++	struct v4l2_mbus_framefmt *sink_ffmt;
++	struct v4l2_rect *crop;
++	int ret = 0;
++
++	if (sd->entity.pads[sel->pad].flags & MEDIA_PAD_FL_SINK)
++		return -EINVAL;
++
++	sink_ffmt = v4l2_subdev_state_get_opposite_stream_format(state,
++								 sel->pad,
++								 sel->stream);
++	if (!sink_ffmt)
++		return -EINVAL;
++
++	crop = v4l2_subdev_state_get_stream_crop(state, sel->pad, sel->stream);
++	if (!crop)
++		return -EINVAL;
++
++	switch (sel->target) {
++	case V4L2_SEL_TGT_CROP_DEFAULT:
++	case V4L2_SEL_TGT_CROP_BOUNDS:
++		sel->r.left = 0;
++		sel->r.top = 0;
++		sel->r.width = sink_ffmt->width;
++		sel->r.height = sink_ffmt->height;
++		break;
++	case V4L2_SEL_TGT_CROP:
++		sel->r = *crop;
++		break;
++	default:
++		ret = -EINVAL;
++	}
++
++	return ret;
++}
++
++static const struct v4l2_subdev_video_ops csi2_sd_video_ops = {
++	.s_stream = set_stream,
++};
++
++static const struct v4l2_subdev_pad_ops csi2_sd_pad_ops = {
++	.init_cfg = ipu6_isys_subdev_init_cfg,
++	.get_fmt = v4l2_subdev_get_fmt,
++	.set_fmt = ipu6_isys_subdev_set_fmt,
++	.get_selection = ipu6_isys_csi2_get_sel,
++	.set_selection = ipu6_isys_csi2_set_sel,
++	.enum_mbus_code = ipu6_isys_subdev_enum_mbus_code,
++	.set_routing = ipu6_isys_subdev_set_routing,
++};
++
++static const struct v4l2_subdev_ops csi2_sd_ops = {
++	.core = &csi2_sd_core_ops,
++	.video = &csi2_sd_video_ops,
++	.pad = &csi2_sd_pad_ops,
++};
++
++static const struct media_entity_operations csi2_entity_ops = {
++	.link_validate = v4l2_subdev_link_validate,
++	.has_pad_interdep = v4l2_subdev_has_pad_interdep,
++};
++
++void ipu6_isys_csi2_cleanup(struct ipu6_isys_csi2 *csi2)
++{
++	if (!csi2->isys)
++		return;
++
++	v4l2_device_unregister_subdev(&csi2->asd.sd);
++	v4l2_subdev_cleanup(&csi2->asd.sd);
++	ipu6_isys_subdev_cleanup(&csi2->asd);
++	csi2->isys = NULL;
++}
++
++int ipu6_isys_csi2_init(struct ipu6_isys_csi2 *csi2,
++			struct ipu6_isys *isys,
++			void __iomem *base, unsigned int index)
++{
++	struct device *dev = &isys->adev->auxdev.dev;
++	int ret;
++
++	csi2->isys = isys;
++	csi2->base = base;
++	csi2->port = index;
++
++	csi2->asd.sd.entity.ops = &csi2_entity_ops;
++	csi2->asd.isys = isys;
++	ret = ipu6_isys_subdev_init(&csi2->asd, &csi2_sd_ops, 0,
++				    NR_OF_CSI2_SINK_PADS, NR_OF_CSI2_SRC_PADS);
++	if (ret)
++		goto fail;
++
++	csi2->asd.source = IPU6_FW_ISYS_STREAM_SRC_CSI2_PORT0 + index;
++	csi2->asd.supported_codes = csi2_supported_codes;
++	snprintf(csi2->asd.sd.name, sizeof(csi2->asd.sd.name),
++		 IPU6_ISYS_ENTITY_PREFIX " CSI2 %u", index);
++	v4l2_set_subdevdata(&csi2->asd.sd, &csi2->asd);
++	ret = v4l2_subdev_init_finalize(&csi2->asd.sd);
++	if (ret) {
++		dev_err(dev, "failed to init v4l2 subdev\n");
++		goto fail;
++	}
++
++	ret = v4l2_device_register_subdev(&isys->v4l2_dev, &csi2->asd.sd);
++	if (ret) {
++		dev_err(dev, "failed to register v4l2 subdev\n");
++		goto fail;
++	}
++
++	return 0;
++
++fail:
++	ipu6_isys_csi2_cleanup(csi2);
++
++	return ret;
++}
++
++void ipu6_isys_csi2_sof_event_by_stream(struct ipu6_isys_stream *stream)
++{
++	struct video_device *vdev = stream->asd->sd.devnode;
++	struct device *dev = &stream->isys->adev->auxdev.dev;
++	struct ipu6_isys_csi2 *csi2 = ipu6_isys_subdev_to_csi2(stream->asd);
++	struct v4l2_event ev = {
++		.type = V4L2_EVENT_FRAME_SYNC,
++	};
++
++	ev.u.frame_sync.frame_sequence = atomic_fetch_inc(&stream->sequence);
++	v4l2_event_queue(vdev, &ev);
++
++	dev_dbg(dev, "sof_event::csi2-%i sequence: %i, vc: %d\n",
++		csi2->port, ev.u.frame_sync.frame_sequence, stream->vc);
++}
++
++void ipu6_isys_csi2_eof_event_by_stream(struct ipu6_isys_stream *stream)
++{
++	struct device *dev = &stream->isys->adev->auxdev.dev;
++	struct ipu6_isys_csi2 *csi2 = ipu6_isys_subdev_to_csi2(stream->asd);
++	u32 frame_sequence = atomic_read(&stream->sequence);
++
++	dev_dbg(dev, "eof_event::csi2-%i sequence: %i\n",
++		csi2->port, frame_sequence);
++}
++
++int ipu6_isys_csi2_get_remote_desc(u32 source_stream,
++				   struct ipu6_isys_csi2 *csi2,
++				   struct media_entity *source_entity,
++				   struct v4l2_mbus_frame_desc_entry *entry,
++				   int *nr_queues)
++{
++	struct v4l2_mbus_frame_desc_entry *desc_entry = NULL;
++	struct device *dev = &csi2->isys->adev->auxdev.dev;
++	struct v4l2_mbus_frame_desc desc;
++	struct v4l2_subdev *source;
++	struct media_pad *pad;
++	unsigned int i;
++	int count = 0;
++	int ret;
++
++	source = media_entity_to_v4l2_subdev(source_entity);
++	if (!source)
++		return -EPIPE;
++
++	pad = media_pad_remote_pad_first(&csi2->asd.pad[CSI2_PAD_SINK]);
++	if (!pad)
++		return -EPIPE;
++
++	ret = v4l2_subdev_call(source, pad, get_frame_desc, pad->index, &desc);
++	if (ret)
++		return ret;
++
++	if (desc.type != V4L2_MBUS_FRAME_DESC_TYPE_CSI2) {
++		dev_err(dev, "Unsupported frame descriptor type\n");
++		return -EINVAL;
++	}
++
++	for (i = 0; i < desc.num_entries; i++) {
++		if (source_stream == desc.entry[i].stream) {
++			desc_entry = &desc.entry[i];
++			break;
++		}
++	}
++
++	if (!desc_entry) {
++		dev_err(dev, "Failed to find stream %u from remote subdev\n",
++			source_stream);
++		return -EINVAL;
++	}
++
++	if (desc_entry->bus.csi2.vc >= NR_OF_CSI2_VC) {
++		dev_err(dev, "invalid vc %d\n", desc_entry->bus.csi2.vc);
++		return -EINVAL;
++	}
++
++	*entry = *desc_entry;
++	for (i = 0; i < desc.num_entries; i++) {
++		if (entry->bus.csi2.vc == desc.entry[i].bus.csi2.vc)
++			count++;
++	}
++
++	*nr_queues = count;
++	return 0;
++}
++
++void ipu6_isys_set_csi2_streams_status(struct ipu6_isys_video *av, bool status)
++{
++	struct ipu6_isys_stream *stream = av->stream;
++	struct v4l2_subdev *sd = &stream->asd->sd;
++	struct v4l2_subdev_state *state;
++	struct media_pad *r_pad;
++	unsigned int i;
++	u32 r_stream;
++
++	r_pad = media_pad_remote_pad_first(&av->pad);
++	r_stream = ipu6_isys_get_src_stream_by_src_pad(sd, r_pad->index);
++
++	state = v4l2_subdev_lock_and_get_active_state(sd);
++
++	for (i = 0; i < state->stream_configs.num_configs; i++) {
++		struct v4l2_subdev_stream_config *cfg =
++			&state->stream_configs.configs[i];
++
++		if (cfg->pad == r_pad->index && r_stream == cfg->stream) {
++			dev_dbg(&av->isys->adev->auxdev.dev,
++				"%s: pad:%u, stream:%u, status:%u\n",
++				sd->entity.name, r_pad->index, r_stream,
++				status);
++			cfg->enabled = status;
++		}
++	}
++
++	v4l2_subdev_unlock_state(state);
++}
+diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys-csi2.h b/drivers/media/pci/intel/ipu6/ipu6-isys-csi2.h
+new file mode 100644
+index 000000000000..d4765bae6112
+--- /dev/null
++++ b/drivers/media/pci/intel/ipu6/ipu6-isys-csi2.h
+@@ -0,0 +1,81 @@
++/* SPDX-License-Identifier: GPL-2.0-only */
++/* Copyright (C) 2013 - 2023 Intel Corporation */
++
++#ifndef IPU6_ISYS_CSI2_H
++#define IPU6_ISYS_CSI2_H
++
++#include <linux/container_of.h>
++
++#include "ipu6-isys-subdev.h"
++
++struct media_entity;
++struct v4l2_mbus_frame_desc_entry;
++
++struct ipu6_isys_video;
++struct ipu6_isys;
++struct ipu6_isys_csi2_pdata;
++struct ipu6_isys_stream;
++
++#define NR_OF_CSI2_VC			16
++#define INVALID_VC_ID			-1
++#define NR_OF_CSI2_SINK_PADS		1
++#define CSI2_PAD_SINK			0
++#define NR_OF_CSI2_SRC_PADS		8
++#define CSI2_PAD_SRC			1
++#define NR_OF_CSI2_PADS	(NR_OF_CSI2_SINK_PADS + NR_OF_CSI2_SRC_PADS)
++
++#define CSI2_CSI_RX_DLY_CNT_TERMEN_CLANE_A		0
++#define CSI2_CSI_RX_DLY_CNT_TERMEN_CLANE_B		0
++#define CSI2_CSI_RX_DLY_CNT_SETTLE_CLANE_A		95
++#define CSI2_CSI_RX_DLY_CNT_SETTLE_CLANE_B		-8
++
++#define CSI2_CSI_RX_DLY_CNT_TERMEN_DLANE_A		0
++#define CSI2_CSI_RX_DLY_CNT_TERMEN_DLANE_B		0
++#define CSI2_CSI_RX_DLY_CNT_SETTLE_DLANE_A		85
++#define CSI2_CSI_RX_DLY_CNT_SETTLE_DLANE_B		-2
++
++struct ipu6_isys_csi2 {
++	struct ipu6_isys_subdev asd;
++	struct ipu6_isys_csi2_pdata *pdata;
++	struct ipu6_isys *isys;
++
++	void __iomem *base;
++	u32 receiver_errors;
++	unsigned int nlanes;
++	unsigned int port;
++	unsigned int stream_count;
++};
++
++struct ipu6_isys_csi2_timing {
++	u32 ctermen;
++	u32 csettle;
++	u32 dtermen;
++	u32 dsettle;
++};
++
++struct ipu6_csi2_error {
++	const char *error_string;
++	bool is_info_only;
++};
++
++#define ipu6_isys_subdev_to_csi2(__sd) \
++	container_of(__sd, struct ipu6_isys_csi2, asd)
++
++#define to_ipu6_isys_csi2(__asd) container_of(__asd, struct ipu6_isys_csi2, asd)
++
++s64 ipu6_isys_csi2_get_link_freq(struct ipu6_isys_csi2 *csi2);
++int ipu6_isys_csi2_init(struct ipu6_isys_csi2 *csi2, struct ipu6_isys *isys,
++			void __iomem *base, unsigned int index);
++void ipu6_isys_csi2_cleanup(struct ipu6_isys_csi2 *csi2);
++void ipu6_isys_csi2_sof_event_by_stream(struct ipu6_isys_stream *stream);
++void ipu6_isys_csi2_eof_event_by_stream(struct ipu6_isys_stream *stream);
++void ipu6_isys_register_errors(struct ipu6_isys_csi2 *csi2);
++void ipu6_isys_csi2_error(struct ipu6_isys_csi2 *csi2);
++int ipu6_isys_csi2_get_remote_desc(u32 source_stream,
++				   struct ipu6_isys_csi2 *csi2,
++				   struct media_entity *source_entity,
++				   struct v4l2_mbus_frame_desc_entry *entry,
++				   int *nr_queues);
++void ipu6_isys_set_csi2_streams_status(struct ipu6_isys_video *av, bool status);
++
++#endif /* IPU6_ISYS_CSI2_H */
+diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys-subdev.c b/drivers/media/pci/intel/ipu6/ipu6-isys-subdev.c
+new file mode 100644
+index 000000000000..510c5ca34f9f
+--- /dev/null
++++ b/drivers/media/pci/intel/ipu6/ipu6-isys-subdev.c
+@@ -0,0 +1,381 @@
++// SPDX-License-Identifier: GPL-2.0-only
++/*
++ * Copyright (C) 2013 - 2023 Intel Corporation
++ */
++
++#include <linux/bug.h>
++#include <linux/device.h>
++#include <linux/minmax.h>
++
++#include <media/media-entity.h>
++#include <media/mipi-csi2.h>
++#include <media/v4l2-ctrls.h>
++#include <media/v4l2-subdev.h>
++
++#include "ipu6-bus.h"
++#include "ipu6-isys.h"
++#include "ipu6-isys-subdev.h"
++
++unsigned int ipu6_isys_mbus_code_to_bpp(u32 code)
++{
++	switch (code) {
++	case MEDIA_BUS_FMT_RGB888_1X24:
++		return 24;
++	case MEDIA_BUS_FMT_RGB565_1X16:
++	case MEDIA_BUS_FMT_UYVY8_1X16:
++	case MEDIA_BUS_FMT_YUYV8_1X16:
++		return 16;
++	case MEDIA_BUS_FMT_SBGGR12_1X12:
++	case MEDIA_BUS_FMT_SGBRG12_1X12:
++	case MEDIA_BUS_FMT_SGRBG12_1X12:
++	case MEDIA_BUS_FMT_SRGGB12_1X12:
++		return 12;
++	case MEDIA_BUS_FMT_SBGGR10_1X10:
++	case MEDIA_BUS_FMT_SGBRG10_1X10:
++	case MEDIA_BUS_FMT_SGRBG10_1X10:
++	case MEDIA_BUS_FMT_SRGGB10_1X10:
++		return 10;
++	case MEDIA_BUS_FMT_SBGGR8_1X8:
++	case MEDIA_BUS_FMT_SGBRG8_1X8:
++	case MEDIA_BUS_FMT_SGRBG8_1X8:
++	case MEDIA_BUS_FMT_SRGGB8_1X8:
++		return 8;
++	default:
++		WARN_ON(1);
++		return 8;
++	}
++}
++
++unsigned int ipu6_isys_mbus_code_to_mipi(u32 code)
++{
++	switch (code) {
++	case MEDIA_BUS_FMT_RGB565_1X16:
++		return MIPI_CSI2_DT_RGB565;
++	case MEDIA_BUS_FMT_RGB888_1X24:
++		return MIPI_CSI2_DT_RGB888;
++	case MEDIA_BUS_FMT_UYVY8_1X16:
++	case MEDIA_BUS_FMT_YUYV8_1X16:
++		return MIPI_CSI2_DT_YUV422_8B;
++	case MEDIA_BUS_FMT_SBGGR12_1X12:
++	case MEDIA_BUS_FMT_SGBRG12_1X12:
++	case MEDIA_BUS_FMT_SGRBG12_1X12:
++	case MEDIA_BUS_FMT_SRGGB12_1X12:
++		return MIPI_CSI2_DT_RAW12;
++	case MEDIA_BUS_FMT_SBGGR10_1X10:
++	case MEDIA_BUS_FMT_SGBRG10_1X10:
++	case MEDIA_BUS_FMT_SGRBG10_1X10:
++	case MEDIA_BUS_FMT_SRGGB10_1X10:
++		return MIPI_CSI2_DT_RAW10;
++	case MEDIA_BUS_FMT_SBGGR8_1X8:
++	case MEDIA_BUS_FMT_SGBRG8_1X8:
++	case MEDIA_BUS_FMT_SGRBG8_1X8:
++	case MEDIA_BUS_FMT_SRGGB8_1X8:
++		return MIPI_CSI2_DT_RAW8;
++	default:
++		/* return unavailable MIPI data type - 0x3f */
++		WARN_ON(1);
++		return 0x3f;
++	}
++}
++
++bool ipu6_isys_is_bayer_format(u32 code)
++{
++	switch (ipu6_isys_mbus_code_to_mipi(code)) {
++	case MIPI_CSI2_DT_RAW8:
++	case MIPI_CSI2_DT_RAW10:
++	case MIPI_CSI2_DT_RAW12:
++		return true;
++	default:
++		return false;
++	}
++}
++
++u32 ipu6_isys_convert_bayer_order(u32 code, int x, int y)
++{
++	static const u32 code_map[] = {
++		MEDIA_BUS_FMT_SRGGB8_1X8,
++		MEDIA_BUS_FMT_SGRBG8_1X8,
++		MEDIA_BUS_FMT_SGBRG8_1X8,
++		MEDIA_BUS_FMT_SBGGR8_1X8,
++		MEDIA_BUS_FMT_SRGGB10_1X10,
++		MEDIA_BUS_FMT_SGRBG10_1X10,
++		MEDIA_BUS_FMT_SGBRG10_1X10,
++		MEDIA_BUS_FMT_SBGGR10_1X10,
++		MEDIA_BUS_FMT_SRGGB12_1X12,
++		MEDIA_BUS_FMT_SGRBG12_1X12,
++		MEDIA_BUS_FMT_SGBRG12_1X12,
++		MEDIA_BUS_FMT_SBGGR12_1X12
++	};
++	u32 i;
++
++	for (i = 0; i < ARRAY_SIZE(code_map); i++)
++		if (code_map[i] == code)
++			break;
++
++	if (WARN_ON(i == ARRAY_SIZE(code_map)))
++		return code;
++
++	return code_map[i ^ (((y & 1) << 1) | (x & 1))];
++}
++
++int ipu6_isys_subdev_set_fmt(struct v4l2_subdev *sd,
++			     struct v4l2_subdev_state *state,
++			     struct v4l2_subdev_format *format)
++{
++	struct ipu6_isys_subdev *asd = to_ipu6_isys_subdev(sd);
++	struct v4l2_mbus_framefmt *fmt;
++	struct v4l2_rect *crop;
++	u32 code = asd->supported_codes[0];
++	u32 other_pad, other_stream;
++	unsigned int i;
++	int ret;
++
++	/* No transcoding, source and sink formats must match. */
++	if ((sd->entity.pads[format->pad].flags & MEDIA_PAD_FL_SOURCE) &&
++	    sd->entity.num_pads > 1)
++		return v4l2_subdev_get_fmt(sd, state, format);
++
++	format->format.width = clamp(format->format.width, IPU6_ISYS_MIN_WIDTH,
++				     IPU6_ISYS_MAX_WIDTH);
++	format->format.height = clamp(format->format.height,
++				      IPU6_ISYS_MIN_HEIGHT,
++				      IPU6_ISYS_MAX_HEIGHT);
++
++	for (i = 0; asd->supported_codes[i]; i++) {
++		if (asd->supported_codes[i] == format->format.code) {
++			code = asd->supported_codes[i];
++			break;
++		}
++	}
++	format->format.code = code;
++	format->format.field = V4L2_FIELD_NONE;
++
++	/* Store the format and propagate it to the source pad. */
++	fmt = v4l2_subdev_state_get_stream_format(state, format->pad,
++						  format->stream);
++	if (!fmt)
++		return -EINVAL;
++
++	*fmt = format->format;
++
++	if (!(sd->entity.pads[format->pad].flags & MEDIA_PAD_FL_SINK))
++		return 0;
++
++	/* propagate format to following source pad */
++	fmt = v4l2_subdev_state_get_opposite_stream_format(state, format->pad,
++							   format->stream);
++	if (!fmt)
++		return -EINVAL;
++
++	*fmt = format->format;
++
++	ret = v4l2_subdev_routing_find_opposite_end(&state->routing,
++						    format->pad,
++						    format->stream,
++						    &other_pad,
++						    &other_stream);
++	if (ret)
++		return -EINVAL;
++
++	crop = v4l2_subdev_state_get_stream_crop(state, other_pad,
++						 other_stream);
++	/* reset crop */
++	crop->left = 0;
++	crop->top = 0;
++	crop->width = fmt->width;
++	crop->height = fmt->height;
++
++	return 0;
++}
++
++int ipu6_isys_subdev_enum_mbus_code(struct v4l2_subdev *sd,
++				    struct v4l2_subdev_state *state,
++				    struct v4l2_subdev_mbus_code_enum *code)
++{
++	struct ipu6_isys_subdev *asd = to_ipu6_isys_subdev(sd);
++	const u32 *supported_codes = asd->supported_codes;
++	u32 index;
++
++	for (index = 0; supported_codes[index]; index++) {
++		if (index == code->index) {
++			code->code = supported_codes[index];
++			return 0;
++		}
++	}
++
++	return -EINVAL;
++}
++
++static int subdev_set_routing(struct v4l2_subdev *sd,
++			      struct v4l2_subdev_state *state,
++			      struct v4l2_subdev_krouting *routing)
++{
++	static const struct v4l2_mbus_framefmt format = {
++		.width = 4096,
++		.height = 3072,
++		.code = MEDIA_BUS_FMT_SGRBG10_1X10,
++		.field = V4L2_FIELD_NONE,
++	};
++	int ret;
++
++	ret = v4l2_subdev_routing_validate(sd, routing,
++					   V4L2_SUBDEV_ROUTING_ONLY_1_TO_1);
++	if (ret)
++		return ret;
++
++	return v4l2_subdev_set_routing_with_fmt(sd, state, routing, &format);
++}
++
++int ipu6_isys_get_stream_pad_fmt(struct v4l2_subdev *sd, u32 pad, u32 stream,
++				 struct v4l2_mbus_framefmt *format)
++{
++	struct v4l2_mbus_framefmt *fmt;
++	struct v4l2_subdev_state *state;
++
++	if (!sd || !format)
++		return -EINVAL;
++
++	state = v4l2_subdev_lock_and_get_active_state(sd);
++	fmt = v4l2_subdev_state_get_stream_format(state, pad, stream);
++	if (fmt)
++		*format = *fmt;
++	v4l2_subdev_unlock_state(state);
++
++	return fmt ? 0 : -EINVAL;
++}
++
++int ipu6_isys_get_stream_pad_crop(struct v4l2_subdev *sd, u32 pad, u32 stream,
++				  struct v4l2_rect *crop)
++{
++	struct v4l2_subdev_state *state;
++	struct v4l2_rect *rect;
++
++	if (!sd || !crop)
++		return -EINVAL;
++
++	state = v4l2_subdev_lock_and_get_active_state(sd);
++	rect = v4l2_subdev_state_get_stream_crop(state, pad, stream);
++	if (rect)
++		*crop = *rect;
++	v4l2_subdev_unlock_state(state);
++
++	return rect ? 0 : -EINVAL;
++}
++
++u32 ipu6_isys_get_src_stream_by_src_pad(struct v4l2_subdev *sd, u32 pad)
++{
++	struct v4l2_subdev_state *state;
++	struct v4l2_subdev_route *routes;
++	unsigned int i;
++	u32 source_stream = 0;
++
++	state = v4l2_subdev_lock_and_get_active_state(sd);
++	if (!state)
++		return 0;
++
++	routes = state->routing.routes;
++	for (i = 0; i < state->routing.num_routes; i++) {
++		if (routes[i].source_pad == pad) {
++			source_stream = routes[i].source_stream;
++			break;
++		}
++	}
++
++	v4l2_subdev_unlock_state(state);
++
++	return source_stream;
++}
++
++int ipu6_isys_subdev_init_cfg(struct v4l2_subdev *sd,
++			      struct v4l2_subdev_state *state)
++{
++	struct v4l2_subdev_route route = {
++		.sink_pad = 0,
++		.sink_stream = 0,
++		.source_pad = 1,
++		.source_stream = 0,
++		.flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE,
++	};
++	struct v4l2_subdev_krouting routing = {
++		.num_routes = 1,
++		.routes = &route,
++	};
++
++	return subdev_set_routing(sd, state, &routing);
++}
++
++int ipu6_isys_subdev_set_routing(struct v4l2_subdev *sd,
++				 struct v4l2_subdev_state *state,
++				 enum v4l2_subdev_format_whence which,
++				 struct v4l2_subdev_krouting *routing)
++{
++	return subdev_set_routing(sd, state, routing);
++}
++
++int ipu6_isys_subdev_init(struct ipu6_isys_subdev *asd,
++			  const struct v4l2_subdev_ops *ops,
++			  unsigned int nr_ctrls,
++			  unsigned int num_sink_pads,
++			  unsigned int num_source_pads)
++{
++	unsigned int num_pads = num_sink_pads + num_source_pads;
++	unsigned int i;
++	int ret;
++
++	v4l2_subdev_init(&asd->sd, ops);
++
++	asd->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE |
++			 V4L2_SUBDEV_FL_HAS_EVENTS |
++			 V4L2_SUBDEV_FL_STREAMS;
++	asd->sd.owner = THIS_MODULE;
++	asd->sd.entity.function = MEDIA_ENT_F_VID_IF_BRIDGE;
++
++	asd->pad = devm_kcalloc(&asd->isys->adev->auxdev.dev, num_pads,
++				sizeof(*asd->pad), GFP_KERNEL);
++
++	if (!asd->pad)
++		return -ENOMEM;
++
++	for (i = 0; i < num_sink_pads; i++)
++		asd->pad[i].flags = MEDIA_PAD_FL_SINK |
++				    MEDIA_PAD_FL_MUST_CONNECT;
++
++	for (i = num_sink_pads; i < num_pads; i++)
++		asd->pad[i].flags = MEDIA_PAD_FL_SOURCE;
++
++	ret = media_entity_pads_init(&asd->sd.entity, num_pads, asd->pad);
++	if (ret)
++		return ret;
++
++	if (asd->ctrl_init) {
++		ret = v4l2_ctrl_handler_init(&asd->ctrl_handler, nr_ctrls);
++		if (ret)
++			goto out_media_entity_cleanup;
++
++		asd->ctrl_init(&asd->sd);
++		if (asd->ctrl_handler.error) {
++			ret = asd->ctrl_handler.error;
++			goto out_v4l2_ctrl_handler_free;
++		}
++
++		asd->sd.ctrl_handler = &asd->ctrl_handler;
++	}
++
++	asd->source = -1;
++
++	return 0;
++
++out_v4l2_ctrl_handler_free:
++	v4l2_ctrl_handler_free(&asd->ctrl_handler);
++
++out_media_entity_cleanup:
++	media_entity_cleanup(&asd->sd.entity);
++
++	return ret;
++}
++
++void ipu6_isys_subdev_cleanup(struct ipu6_isys_subdev *asd)
++{
++	media_entity_cleanup(&asd->sd.entity);
++	v4l2_ctrl_handler_free(&asd->ctrl_handler);
++}
+diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys-subdev.h b/drivers/media/pci/intel/ipu6/ipu6-isys-subdev.h
+new file mode 100644
+index 000000000000..adea2a55761d
+--- /dev/null
++++ b/drivers/media/pci/intel/ipu6/ipu6-isys-subdev.h
+@@ -0,0 +1,61 @@
++/* SPDX-License-Identifier: GPL-2.0-only */
++/* Copyright (C) 2013 - 2023 Intel Corporation */
++
++#ifndef IPU6_ISYS_SUBDEV_H
++#define IPU6_ISYS_SUBDEV_H
++
++#include <linux/container_of.h>
++
++#include <media/media-entity.h>
++#include <media/v4l2-ctrls.h>
++#include <media/v4l2-subdev.h>
++
++struct ipu6_isys;
++
++struct ipu6_isys_subdev {
++	struct v4l2_subdev sd;
++	struct ipu6_isys *isys;
++	u32 const *supported_codes;
++	struct media_pad *pad;
++	struct v4l2_ctrl_handler ctrl_handler;
++	void (*ctrl_init)(struct v4l2_subdev *sd);
++	int source;	/* SSI stream source; -1 if unset */
++};
++
++#define to_ipu6_isys_subdev(__sd) \
++	container_of(__sd, struct ipu6_isys_subdev, sd)
++
++unsigned int ipu6_isys_mbus_code_to_bpp(u32 code);
++unsigned int ipu6_isys_mbus_code_to_mipi(u32 code);
++bool ipu6_isys_is_bayer_format(u32 code);
++u32 ipu6_isys_convert_bayer_order(u32 code, int x, int y);
++
++int ipu6_isys_subdev_set_fmt(struct v4l2_subdev *sd,
++			     struct v4l2_subdev_state *state,
++			     struct v4l2_subdev_format *fmt);
++int ipu6_isys_subdev_enum_mbus_code(struct v4l2_subdev *sd,
++				    struct v4l2_subdev_state *state,
++				    struct v4l2_subdev_mbus_code_enum
++				    *code);
++int ipu6_isys_subdev_link_validate(struct v4l2_subdev *sd,
++				   struct media_link *link,
++				   struct v4l2_subdev_format *source_fmt,
++				   struct v4l2_subdev_format *sink_fmt);
++u32 ipu6_isys_get_src_stream_by_src_pad(struct v4l2_subdev *sd, u32 pad);
++int ipu6_isys_get_stream_pad_fmt(struct v4l2_subdev *sd, u32 pad, u32 stream,
++				 struct v4l2_mbus_framefmt *format);
++int ipu6_isys_get_stream_pad_crop(struct v4l2_subdev *sd, u32 pad, u32 stream,
++				  struct v4l2_rect *crop);
++int ipu6_isys_subdev_init_cfg(struct v4l2_subdev *sd,
++			      struct v4l2_subdev_state *state);
++int ipu6_isys_subdev_set_routing(struct v4l2_subdev *sd,
++				 struct v4l2_subdev_state *state,
++				 enum v4l2_subdev_format_whence which,
++				 struct v4l2_subdev_krouting *routing);
++int ipu6_isys_subdev_init(struct ipu6_isys_subdev *asd,
++			  const struct v4l2_subdev_ops *ops,
++			  unsigned int nr_ctrls,
++			  unsigned int num_sink_pads,
++			  unsigned int num_source_pads);
++void ipu6_isys_subdev_cleanup(struct ipu6_isys_subdev *asd);
++#endif /* IPU6_ISYS_SUBDEV_H */
+diff --git a/drivers/media/pci/intel/ipu6/ipu6-platform-isys-csi2-reg.h b/drivers/media/pci/intel/ipu6/ipu6-platform-isys-csi2-reg.h
+new file mode 100644
+index 000000000000..2034e1109d98
+--- /dev/null
++++ b/drivers/media/pci/intel/ipu6/ipu6-platform-isys-csi2-reg.h
+@@ -0,0 +1,189 @@
++/* SPDX-License-Identifier: GPL-2.0-only */
++/* Copyright (C) 2023 Intel Corporation */
++
++#ifndef IPU6_PLATFORM_ISYS_CSI2_REG_H
++#define IPU6_PLATFORM_ISYS_CSI2_REG_H
++
++#include <linux/bits.h>
++
++#define CSI_REG_BASE			0x220000
++#define CSI_REG_BASE_PORT(id)		((id) * 0x1000)
++
++#define IPU6_CSI_PORT_A_ADDR_OFFSET	\
++		(CSI_REG_BASE + CSI_REG_BASE_PORT(0))
++#define IPU6_CSI_PORT_B_ADDR_OFFSET	\
++		(CSI_REG_BASE + CSI_REG_BASE_PORT(1))
++#define IPU6_CSI_PORT_C_ADDR_OFFSET	\
++		(CSI_REG_BASE + CSI_REG_BASE_PORT(2))
++#define IPU6_CSI_PORT_D_ADDR_OFFSET	\
++		(CSI_REG_BASE + CSI_REG_BASE_PORT(3))
++#define IPU6_CSI_PORT_E_ADDR_OFFSET	\
++		(CSI_REG_BASE + CSI_REG_BASE_PORT(4))
++#define IPU6_CSI_PORT_F_ADDR_OFFSET	\
++		(CSI_REG_BASE + CSI_REG_BASE_PORT(5))
++#define IPU6_CSI_PORT_G_ADDR_OFFSET	\
++		(CSI_REG_BASE + CSI_REG_BASE_PORT(6))
++#define IPU6_CSI_PORT_H_ADDR_OFFSET	\
++		(CSI_REG_BASE + CSI_REG_BASE_PORT(7))
++
++/* CSI Port Genral Purpose Registers */
++#define CSI_REG_PORT_GPREG_SRST                 0x0
++#define CSI_REG_PORT_GPREG_CSI2_SLV_REG_SRST    0x4
++#define CSI_REG_PORT_GPREG_CSI2_PORT_CONTROL    0x8
++
++/*
++ * Port IRQs mapping events:
++ * IRQ0 - CSI_FE event
++ * IRQ1 - CSI_SYNC
++ * IRQ2 - S2M_SIDS0TO7
++ * IRQ3 - S2M_SIDS8TO15
++ */
++#define CSI_PORT_REG_BASE_IRQ_CSI               0x80
++#define CSI_PORT_REG_BASE_IRQ_CSI_SYNC          0xA0
++#define CSI_PORT_REG_BASE_IRQ_S2M_SIDS0TOS7     0xC0
++#define CSI_PORT_REG_BASE_IRQ_S2M_SIDS8TOS15    0xE0
++
++#define CSI_PORT_REG_BASE_IRQ_EDGE_OFFSET	0x0
++#define CSI_PORT_REG_BASE_IRQ_MASK_OFFSET	0x4
++#define CSI_PORT_REG_BASE_IRQ_STATUS_OFFSET	0x8
++#define CSI_PORT_REG_BASE_IRQ_CLEAR_OFFSET	0xc
++#define CSI_PORT_REG_BASE_IRQ_ENABLE_OFFSET	0x10
++#define CSI_PORT_REG_BASE_IRQ_LEVEL_NOT_PULSE_OFFSET	0x14
++
++#define IPU6SE_CSI_RX_ERROR_IRQ_MASK		GENMASK(18, 0)
++#define IPU6_CSI_RX_ERROR_IRQ_MASK		GENMASK(19, 0)
++
++#define CSI_RX_NUM_ERRORS_IN_IRQ		20
++#define CSI_RX_NUM_IRQ				32
++
++#define IPU_CSI_RX_IRQ_FS_VC(chn)	(1 << ((chn) * 2))
++#define IPU_CSI_RX_IRQ_FE_VC(chn)	(2 << ((chn) * 2))
++
++/* PPI2CSI */
++#define CSI_REG_PPI2CSI_ENABLE				0x200
++#define CSI_REG_PPI2CSI_CONFIG_PPI_INTF			0x204
++#define PPI_INTF_CONFIG_NOF_ENABLED_DLANES_MASK		GENMASK(4, 3)
++#define CSI_REG_PPI2CSI_CONFIG_CSI_FEATURE		0x208
++
++enum CSI_PPI2CSI_CTRL {
++	CSI_PPI2CSI_DISABLE = 0,
++	CSI_PPI2CSI_ENABLE = 1,
++};
++
++/* CSI_FE */
++#define CSI_REG_CSI_FE_ENABLE                   0x280
++#define CSI_REG_CSI_FE_MODE                     0x284
++#define CSI_REG_CSI_FE_MUX_CTRL                 0x288
++#define CSI_REG_CSI_FE_SYNC_CNTR_SEL            0x290
++
++enum CSI_FE_ENABLE_TYPE {
++	CSI_FE_DISABLE = 0,
++	CSI_FE_ENABLE = 1,
++};
++
++enum CSI_FE_MODE_TYPE {
++	CSI_FE_DPHY_MODE = 0,
++	CSI_FE_CPHY_MODE = 1,
++};
++
++enum CSI_FE_INPUT_SELECTOR {
++	CSI_SENSOR_INPUT = 0,
++	CSI_MIPIGEN_INPUT = 1,
++};
++
++enum CSI_FE_SYNC_CNTR_SEL_TYPE {
++	CSI_CNTR_SENSOR_LINE_ID = BIT(0),
++	CSI_CNTR_INT_LINE_PKT_ID = ~CSI_CNTR_SENSOR_LINE_ID,
++	CSI_CNTR_SENSOR_FRAME_ID = BIT(1),
++	CSI_CNTR_INT_FRAME_PKT_ID = ~CSI_CNTR_SENSOR_FRAME_ID,
++};
++
++/* CSI HUB General Purpose Registers */
++#define CSI_REG_HUB_GPREG_SRST			(CSI_REG_BASE + 0x18000)
++#define CSI_REG_HUB_GPREG_SLV_REG_SRST		(CSI_REG_BASE + 0x18004)
++
++#define CSI_REG_HUB_DRV_ACCESS_PORT(id)	(CSI_REG_BASE + 0x18018 + (id) * 4)
++#define CSI_REG_HUB_FW_ACCESS_PORT_OFS		0x17000
++#define CSI_REG_HUB_FW_ACCESS_PORT_V6OFS	0x16000
++#define CSI_REG_HUB_FW_ACCESS_PORT(ofs, id)	\
++					(CSI_REG_BASE + (ofs) + (id) * 4)
++
++enum CSI_PORT_CLK_GATING_SWITCH {
++	CSI_PORT_CLK_GATING_OFF = 0,
++	CSI_PORT_CLK_GATING_ON = 1,
++};
++
++#define CSI_REG_BASE_HUB_IRQ                        0x18200
++
++#define IPU6_REG_ISYS_CSI_TOP_CTRL0_IRQ_EDGE			0x238200
++#define IPU6_REG_ISYS_CSI_TOP_CTRL0_IRQ_MASK			0x238204
++#define IPU6_REG_ISYS_CSI_TOP_CTRL0_IRQ_STATUS			0x238208
++#define IPU6_REG_ISYS_CSI_TOP_CTRL0_IRQ_CLEAR			0x23820c
++#define IPU6_REG_ISYS_CSI_TOP_CTRL0_IRQ_ENABLE			0x238210
++#define IPU6_REG_ISYS_CSI_TOP_CTRL0_IRQ_LEVEL_NOT_PULSE		0x238214
++
++#define IPU6_REG_ISYS_CSI_TOP_CTRL1_IRQ_EDGE			0x238220
++#define IPU6_REG_ISYS_CSI_TOP_CTRL1_IRQ_MASK			0x238224
++#define IPU6_REG_ISYS_CSI_TOP_CTRL1_IRQ_STATUS			0x238228
++#define IPU6_REG_ISYS_CSI_TOP_CTRL1_IRQ_CLEAR			0x23822c
++#define IPU6_REG_ISYS_CSI_TOP_CTRL1_IRQ_ENABLE			0x238230
++#define IPU6_REG_ISYS_CSI_TOP_CTRL1_IRQ_LEVEL_NOT_PULSE		0x238234
++
++/* MTL IPU6V6 irq ctrl0 & ctrl1 */
++#define IPU6V6_REG_ISYS_CSI_TOP_CTRL0_IRQ_EDGE			0x238700
++#define IPU6V6_REG_ISYS_CSI_TOP_CTRL0_IRQ_MASK			0x238704
++#define IPU6V6_REG_ISYS_CSI_TOP_CTRL0_IRQ_STATUS		0x238708
++#define IPU6V6_REG_ISYS_CSI_TOP_CTRL0_IRQ_CLEAR			0x23870c
++#define IPU6V6_REG_ISYS_CSI_TOP_CTRL0_IRQ_ENABLE		0x238710
++#define IPU6V6_REG_ISYS_CSI_TOP_CTRL0_IRQ_LEVEL_NOT_PULSE	0x238714
++
++#define IPU6V6_REG_ISYS_CSI_TOP_CTRL1_IRQ_EDGE			0x238720
++#define IPU6V6_REG_ISYS_CSI_TOP_CTRL1_IRQ_MASK			0x238724
++#define IPU6V6_REG_ISYS_CSI_TOP_CTRL1_IRQ_STATUS		0x238728
++#define IPU6V6_REG_ISYS_CSI_TOP_CTRL1_IRQ_CLEAR			0x23872c
++#define IPU6V6_REG_ISYS_CSI_TOP_CTRL1_IRQ_ENABLE		0x238730
++#define IPU6V6_REG_ISYS_CSI_TOP_CTRL1_IRQ_LEVEL_NOT_PULSE	0x238734
++
++/*
++ * 3:0 CSI_PORT.irq_out[3:0] CSI_PORT_CTRL0 IRQ outputs (4bits)
++ * [0] CSI_PORT.IRQ_CTRL0_csi
++ * [1] CSI_PORT.IRQ_CTRL1_csi_sync
++ * [2] CSI_PORT.IRQ_CTRL2_s2m_sids0to7
++ * [3] CSI_PORT.IRQ_CTRL3_s2m_sids8to15
++ */
++#define IPU6_ISYS_UNISPART_IRQ_CSI2(port)		\
++				   (0x3 << ((port) * IPU6_CSI_IRQ_NUM_PER_PIPE))
++
++/*
++ * ipu6se support 2 front ends, 2 port per front end, 4 ports 0..3
++ * sip0 - 0, 1
++ * sip1 - 2, 3
++ * 0 and 2 support 4 data lanes, 1 and 3 support 2 data lanes
++ * all offset are base from isys base address
++ */
++
++#define CSI2_HUB_GPREG_SIP_SRST(sip)			(0x238038 + (sip) * 4)
++#define CSI2_HUB_GPREG_SIP_FB_PORT_CFG(sip)		(0x238050 + (sip) * 4)
++
++#define CSI2_HUB_GPREG_DPHY_TIMER_INCR			0x238040
++#define CSI2_HUB_GPREG_HPLL_FREQ			0x238044
++#define CSI2_HUB_GPREG_IS_CLK_RATIO			0x238048
++#define CSI2_HUB_GPREG_HPLL_FREQ_ISCLK_RATE_OVERRIDE	0x23804c
++#define CSI2_HUB_GPREG_PORT_CLKGATING_DISABLE		0x238058
++#define CSI2_HUB_GPREG_SIP0_CSI_RX_A_CONTROL		0x23805c
++#define CSI2_HUB_GPREG_SIP0_CSI_RX_B_CONTROL		0x238088
++#define CSI2_HUB_GPREG_SIP1_CSI_RX_A_CONTROL		0x2380a4
++#define CSI2_HUB_GPREG_SIP1_CSI_RX_B_CONTROL		0x2380d0
++
++#define CSI2_SIP_TOP_CSI_RX_BASE(sip)		(0x23805c + (sip) * 0x48)
++#define CSI2_SIP_TOP_CSI_RX_PORT_BASE_0(port)	(0x23805c + ((port) / 2) * 0x48)
++#define CSI2_SIP_TOP_CSI_RX_PORT_BASE_1(port)	(0x238088 + ((port) / 2) * 0x48)
++
++/* offset from port base */
++#define CSI2_SIP_TOP_CSI_RX_PORT_CONTROL		0x0
++#define CSI2_SIP_TOP_CSI_RX_DLY_CNT_TERMEN_CLANE	0x4
++#define CSI2_SIP_TOP_CSI_RX_DLY_CNT_SETTLE_CLANE	0x8
++#define CSI2_SIP_TOP_CSI_RX_DLY_CNT_TERMEN_DLANE(lane)	(0xc + (lane) * 8)
++#define CSI2_SIP_TOP_CSI_RX_DLY_CNT_SETTLE_DLANE(lane)	(0x10 + (lane) * 8)
++
++#endif /* IPU6_ISYS_CSI2_REG_H */
+-- 
+2.43.2
+
+
+From 71d3da5e8f1ea094e26030a12ceed4553cbe182a Mon Sep 17 00:00:00 2001
+From: Bingbu Cao <bingbu.cao@intel.com>
+Date: Thu, 11 Jan 2024 14:55:23 +0800
+Subject: [PATCH 16/33] media: intel/ipu6: add the CSI2 DPHY implementation
+
+IPU6 CSI2 DPHY hardware varies on different platforms, current
+IPU6 has three DPHY hardware instance which maybe used on tigerlake,
+alderlake, metorlake and jasperlake. MCD DPHY is shipped on tigerlake
+and alderlake, DWC DPHY is shipped on metorlake.
+
+Each PHY has its own register space, input system driver call the
+DPHY callback which was set at isys_probe().
+
+Signed-off-by: Bingbu Cao <bingbu.cao@intel.com>
+---
+ .../media/pci/intel/ipu6/ipu6-isys-dwc-phy.c  | 536 +++++++++++++
+ .../media/pci/intel/ipu6/ipu6-isys-jsl-phy.c  | 242 ++++++
+ .../media/pci/intel/ipu6/ipu6-isys-mcd-phy.c  | 720 ++++++++++++++++++
+ 3 files changed, 1498 insertions(+)
+ create mode 100644 drivers/media/pci/intel/ipu6/ipu6-isys-dwc-phy.c
+ create mode 100644 drivers/media/pci/intel/ipu6/ipu6-isys-jsl-phy.c
+ create mode 100644 drivers/media/pci/intel/ipu6/ipu6-isys-mcd-phy.c
+
+diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys-dwc-phy.c b/drivers/media/pci/intel/ipu6/ipu6-isys-dwc-phy.c
+new file mode 100644
+index 000000000000..a4bb5ff51d4e
+--- /dev/null
++++ b/drivers/media/pci/intel/ipu6/ipu6-isys-dwc-phy.c
+@@ -0,0 +1,536 @@
++// SPDX-License-Identifier: GPL-2.0-only
++/*
++ * Copyright (C) 2013 - 2023 Intel Corporation
++ */
++
++#include <linux/bitfield.h>
++#include <linux/bits.h>
++#include <linux/delay.h>
++#include <linux/device.h>
++#include <linux/iopoll.h>
++#include <linux/math64.h>
++
++#include "ipu6-bus.h"
++#include "ipu6-isys.h"
++#include "ipu6-platform-isys-csi2-reg.h"
++
++#define IPU6_DWC_DPHY_BASE(i)			(0x238038 + 0x34 * (i))
++#define IPU6_DWC_DPHY_RSTZ			0x00
++#define IPU6_DWC_DPHY_SHUTDOWNZ			0x04
++#define IPU6_DWC_DPHY_HSFREQRANGE		0x08
++#define IPU6_DWC_DPHY_CFGCLKFREQRANGE		0x0c
++#define IPU6_DWC_DPHY_TEST_IFC_ACCESS_MODE	0x10
++#define IPU6_DWC_DPHY_TEST_IFC_REQ		0x14
++#define IPU6_DWC_DPHY_TEST_IFC_REQ_COMPLETION	0x18
++#define IPU6_DWC_DPHY_DFT_CTRL0			0x28
++#define IPU6_DWC_DPHY_DFT_CTRL1			0x2c
++#define IPU6_DWC_DPHY_DFT_CTRL2			0x30
++
++/*
++ * test IFC request definition:
++ * - req: 0 for read, 1 for write
++ * - 12 bits address
++ * - 8bits data (will ignore for read)
++ * --24----16------4-----0
++ * --|-data-|-addr-|-req-|
++ */
++#define IFC_REQ(req, addr, data) (FIELD_PREP(GENMASK(23, 16), data) | \
++				  FIELD_PREP(GENMASK(15, 4), addr) | \
++				  FIELD_PREP(GENMASK(1, 0), req))
++
++#define TEST_IFC_REQ_READ	0
++#define TEST_IFC_REQ_WRITE	1
++#define TEST_IFC_REQ_RESET	2
++
++#define TEST_IFC_ACCESS_MODE_FSM	0
++#define TEST_IFC_ACCESS_MODE_IFC_CTL	1
++
++enum phy_fsm_state {
++	PHY_FSM_STATE_POWERON = 0,
++	PHY_FSM_STATE_BGPON = 1,
++	PHY_FSM_STATE_CAL_TYPE = 2,
++	PHY_FSM_STATE_BURNIN_CAL = 3,
++	PHY_FSM_STATE_TERMCAL = 4,
++	PHY_FSM_STATE_OFFSETCAL = 5,
++	PHY_FSM_STATE_OFFSET_LANE = 6,
++	PHY_FSM_STATE_IDLE = 7,
++	PHY_FSM_STATE_ULP = 8,
++	PHY_FSM_STATE_DDLTUNNING = 9,
++	PHY_FSM_STATE_SKEW_BACKWARD = 10,
++	PHY_FSM_STATE_INVALID,
++};
++
++static void dwc_dphy_write(struct ipu6_isys *isys, u32 phy_id, u32 addr,
++			   u32 data)
++{
++	struct device *dev = &isys->adev->auxdev.dev;
++	void __iomem *isys_base = isys->pdata->base;
++	void __iomem *base = isys_base + IPU6_DWC_DPHY_BASE(phy_id);
++
++	dev_dbg(dev, "write: reg 0x%lx = data 0x%x", base + addr - isys_base,
++		data);
++	writel(data, base + addr);
++}
++
++static u32 dwc_dphy_read(struct ipu6_isys *isys, u32 phy_id, u32 addr)
++{
++	struct device *dev = &isys->adev->auxdev.dev;
++	void __iomem *isys_base = isys->pdata->base;
++	void __iomem *base = isys_base + IPU6_DWC_DPHY_BASE(phy_id);
++	u32 data;
++
++	data = readl(base + addr);
++	dev_dbg(dev, "read: reg 0x%lx = data 0x%x", base + addr - isys_base,
++		data);
++
++	return data;
++}
++
++static void dwc_dphy_write_mask(struct ipu6_isys *isys, u32 phy_id, u32 addr,
++				u32 data, u8 shift, u8 width)
++{
++	u32 temp;
++	u32 mask;
++
++	mask = (1 << width) - 1;
++	temp = dwc_dphy_read(isys, phy_id, addr);
++	temp &= ~(mask << shift);
++	temp |= (data & mask) << shift;
++	dwc_dphy_write(isys, phy_id, addr, temp);
++}
++
++static u32 __maybe_unused dwc_dphy_read_mask(struct ipu6_isys *isys, u32 phy_id,
++					     u32 addr, u8 shift,  u8 width)
++{
++	u32 val;
++
++	val = dwc_dphy_read(isys, phy_id, addr) >> shift;
++	return val & ((1 << width) - 1);
++}
++
++#define DWC_DPHY_TIMEOUT (5 * USEC_PER_SEC)
++static int dwc_dphy_ifc_read(struct ipu6_isys *isys, u32 phy_id, u32 addr,
++			     u32 *val)
++{
++	struct device *dev = &isys->adev->auxdev.dev;
++	void __iomem *isys_base = isys->pdata->base;
++	void __iomem *base = isys_base + IPU6_DWC_DPHY_BASE(phy_id);
++	void __iomem *reg;
++	u32 completion;
++	int ret;
++
++	dwc_dphy_write(isys, phy_id, IPU6_DWC_DPHY_TEST_IFC_REQ,
++		       IFC_REQ(TEST_IFC_REQ_READ, addr, 0));
++	reg = base + IPU6_DWC_DPHY_TEST_IFC_REQ_COMPLETION;
++	ret = readl_poll_timeout(reg, completion, !(completion & BIT(0)),
++				 10, DWC_DPHY_TIMEOUT);
++	if (ret) {
++		dev_err(dev, "DWC ifc request read timeout\n");
++		return ret;
++	}
++
++	*val = completion >> 8 & 0xff;
++	*val = FIELD_GET(GENMASK(15, 8), completion);
++	dev_dbg(dev, "DWC ifc read 0x%x = 0x%x", addr, *val);
++
++	return 0;
++}
++
++static int dwc_dphy_ifc_write(struct ipu6_isys *isys, u32 phy_id, u32 addr,
++			      u32 data)
++{
++	struct device *dev = &isys->adev->auxdev.dev;
++	void __iomem *isys_base = isys->pdata->base;
++	void __iomem *base = isys_base + IPU6_DWC_DPHY_BASE(phy_id);
++	void __iomem *reg;
++	u32 completion;
++	int ret;
++
++	dwc_dphy_write(isys, phy_id, IPU6_DWC_DPHY_TEST_IFC_REQ,
++		       IFC_REQ(TEST_IFC_REQ_WRITE, addr, data));
++	completion = readl(base + IPU6_DWC_DPHY_TEST_IFC_REQ_COMPLETION);
++	reg = base + IPU6_DWC_DPHY_TEST_IFC_REQ_COMPLETION;
++	ret = readl_poll_timeout(reg, completion, !(completion & BIT(0)),
++				 10, DWC_DPHY_TIMEOUT);
++	if (ret)
++		dev_err(dev, "DWC ifc request write timeout\n");
++
++	return ret;
++}
++
++static void dwc_dphy_ifc_write_mask(struct ipu6_isys *isys, u32 phy_id,
++				    u32 addr, u32 data, u8 shift, u8 width)
++{
++	u32 temp, mask;
++	int ret;
++
++	ret = dwc_dphy_ifc_read(isys, phy_id, addr, &temp);
++	if (ret)
++		return;
++
++	mask = (1 << width) - 1;
++	temp &= ~(mask << shift);
++	temp |= (data & mask) << shift;
++	dwc_dphy_ifc_write(isys, phy_id, addr, temp);
++}
++
++static u32 dwc_dphy_ifc_read_mask(struct ipu6_isys *isys, u32 phy_id, u32 addr,
++				  u8 shift, u8 width)
++{
++	int ret;
++	u32 val;
++
++	ret = dwc_dphy_ifc_read(isys, phy_id, addr, &val);
++	if (ret)
++		return 0;
++
++	return ((val >> shift) & ((1 << width) - 1));
++}
++
++static int dwc_dphy_pwr_up(struct ipu6_isys *isys, u32 phy_id)
++{
++	struct device *dev = &isys->adev->auxdev.dev;
++	u32 fsm_state;
++	int ret;
++
++	dwc_dphy_write(isys, phy_id, IPU6_DWC_DPHY_RSTZ, 1);
++	usleep_range(10, 20);
++	dwc_dphy_write(isys, phy_id, IPU6_DWC_DPHY_SHUTDOWNZ, 1);
++
++	ret = read_poll_timeout(dwc_dphy_ifc_read_mask, fsm_state,
++				(fsm_state == PHY_FSM_STATE_IDLE ||
++				 fsm_state == PHY_FSM_STATE_ULP),
++				100, DWC_DPHY_TIMEOUT, false, isys,
++				phy_id, 0x1e, 0, 4);
++
++	if (ret)
++		dev_err(dev, "Dphy %d power up failed, state 0x%x", phy_id,
++			fsm_state);
++
++	return ret;
++}
++
++struct dwc_dphy_freq_range {
++	u8 hsfreq;
++	u16 min;
++	u16 max;
++	u16 default_mbps;
++	u16 osc_freq_target;
++};
++
++#define DPHY_FREQ_RANGE_NUM		(63)
++#define DPHY_FREQ_RANGE_INVALID_INDEX	(0xff)
++static const struct dwc_dphy_freq_range freqranges[DPHY_FREQ_RANGE_NUM] = {
++	{0x00,	80,	97,	80,	335},
++	{0x10,	80,	107,	90,	335},
++	{0x20,	84,	118,	100,	335},
++	{0x30,	93,	128,	110,	335},
++	{0x01,	103,	139,	120,	335},
++	{0x11,	112,	149,	130,	335},
++	{0x21,	122,	160,	140,	335},
++	{0x31,	131,	170,	150,	335},
++	{0x02,	141,	181,	160,	335},
++	{0x12,	150,	191,	170,	335},
++	{0x22,	160,	202,	180,	335},
++	{0x32,	169,	212,	190,	335},
++	{0x03,	183,	228,	205,	335},
++	{0x13,	198,	244,	220,	335},
++	{0x23,	212,	259,	235,	335},
++	{0x33,	226,	275,	250,	335},
++	{0x04,	250,	301,	275,	335},
++	{0x14,	274,	328,	300,	335},
++	{0x25,	297,	354,	325,	335},
++	{0x35,	321,	380,	350,	335},
++	{0x05,	369,	433,	400,	335},
++	{0x16,	416,	485,	450,	335},
++	{0x26,	464,	538,	500,	335},
++	{0x37,	511,	590,	550,	335},
++	{0x07,	559,	643,	600,	335},
++	{0x18,	606,	695,	650,	335},
++	{0x28,	654,	748,	700,	335},
++	{0x39,	701,	800,	750,	335},
++	{0x09,	749,	853,	800,	335},
++	{0x19,	796,	905,	850,	335},
++	{0x29,	844,	958,	900,	335},
++	{0x3a,	891,	1010,	950,	335},
++	{0x0a,	939,	1063,	1000,	335},
++	{0x1a,	986,	1115,	1050,	335},
++	{0x2a,	1034,	1168,	1100,	335},
++	{0x3b,	1081,	1220,	1150,	335},
++	{0x0b,	1129,	1273,	1200,	335},
++	{0x1b,	1176,	1325,	1250,	335},
++	{0x2b,	1224,	1378,	1300,	335},
++	{0x3c,	1271,	1430,	1350,	335},
++	{0x0c,	1319,	1483,	1400,	335},
++	{0x1c,	1366,	1535,	1450,	335},
++	{0x2c,	1414,	1588,	1500,	335},
++	{0x3d,	1461,	1640,	1550,	208},
++	{0x0d,	1509,	1693,	1600,	214},
++	{0x1d,	1556,	1745,	1650,	221},
++	{0x2e,	1604,	1798,	1700,	228},
++	{0x3e,	1651,	1850,	1750,	234},
++	{0x0e,	1699,	1903,	1800,	241},
++	{0x1e,	1746,	1955,	1850,	248},
++	{0x2f,	1794,	2008,	1900,	255},
++	{0x3f,	1841,	2060,	1950,	261},
++	{0x0f,	1889,	2113,	2000,	268},
++	{0x40,	1936,	2165,	2050,	275},
++	{0x41,	1984,	2218,	2100,	281},
++	{0x42,	2031,	2270,	2150,	288},
++	{0x43,	2079,	2323,	2200,	294},
++	{0x44,	2126,	2375,	2250,	302},
++	{0x45,	2174,	2428,	2300,	308},
++	{0x46,	2221,	2480,	2350,	315},
++	{0x47,	2269,	2500,	2400,	321},
++	{0x48,	2316,	2500,	2450,	328},
++	{0x49,	2364,	2500,	2500,	335}
++};
++
++static u16 get_hsfreq_by_mbps(u32 mbps)
++{
++	unsigned int i = DPHY_FREQ_RANGE_NUM;
++
++	while (i--) {
++		if (freqranges[i].default_mbps == mbps ||
++		    (mbps >= freqranges[i].min && mbps <= freqranges[i].max))
++			return i;
++	}
++
++	return DPHY_FREQ_RANGE_INVALID_INDEX;
++}
++
++static int ipu6_isys_dwc_phy_config(struct ipu6_isys *isys,
++				    u32 phy_id, u32 mbps)
++{
++	struct ipu6_bus_device *adev = isys->adev;
++	struct device *dev = &adev->auxdev.dev;
++	struct ipu6_device *isp = adev->isp;
++	u32 cfg_clk_freqrange;
++	u32 osc_freq_target;
++	u32 index;
++
++	dev_dbg(dev, "config Dphy %u with %u mbps", phy_id, mbps);
++
++	index = get_hsfreq_by_mbps(mbps);
++	if (index == DPHY_FREQ_RANGE_INVALID_INDEX) {
++		dev_err(dev, "link freq not found for mbps %u", mbps);
++		return -EINVAL;
++	}
++
++	dwc_dphy_write_mask(isys, phy_id, IPU6_DWC_DPHY_HSFREQRANGE,
++			    freqranges[index].hsfreq, 0, 7);
++
++	/* Force termination Calibration */
++	if (isys->phy_termcal_val) {
++		dwc_dphy_ifc_write_mask(isys, phy_id, 0x20a, 0x1, 0, 1);
++		dwc_dphy_ifc_write_mask(isys, phy_id, 0x209, 0x3, 0, 2);
++		dwc_dphy_ifc_write_mask(isys, phy_id, 0x209,
++					isys->phy_termcal_val, 4, 4);
++	}
++
++	/*
++	 * Enable override to configure the DDL target oscillation
++	 * frequency on bit 0 of register 0xe4
++	 */
++	dwc_dphy_ifc_write_mask(isys, phy_id, 0xe4, 0x1, 0, 1);
++	/*
++	 * configure registers 0xe2, 0xe3 with the
++	 * appropriate DDL target oscillation frequency
++	 * 0x1cc(460)
++	 */
++	osc_freq_target = freqranges[index].osc_freq_target;
++	dwc_dphy_ifc_write_mask(isys, phy_id, 0xe2,
++				osc_freq_target & 0xff, 0, 8);
++	dwc_dphy_ifc_write_mask(isys, phy_id, 0xe3,
++				(osc_freq_target >> 8) & 0xf, 0, 4);
++
++	if (mbps < 1500) {
++		/* deskew_polarity_rw, for < 1.5Gbps */
++		dwc_dphy_ifc_write_mask(isys, phy_id, 0x8, 0x1, 5, 1);
++	}
++
++	/*
++	 * Set cfgclkfreqrange[5:0] = round[(Fcfg_clk(MHz)-17)*4]
++	 * (38.4 - 17) * 4 = ~85 (0x55)
++	 */
++	cfg_clk_freqrange = (isp->buttress.ref_clk - 170) * 4 / 10;
++	dev_dbg(dev, "ref_clk = %u clk_freqrange = %u",
++		isp->buttress.ref_clk, cfg_clk_freqrange);
++	dwc_dphy_write_mask(isys, phy_id, IPU6_DWC_DPHY_CFGCLKFREQRANGE,
++			    cfg_clk_freqrange, 0, 8);
++
++	dwc_dphy_write_mask(isys, phy_id, IPU6_DWC_DPHY_DFT_CTRL2, 0x1, 4, 1);
++	dwc_dphy_write_mask(isys, phy_id, IPU6_DWC_DPHY_DFT_CTRL2, 0x1, 8, 1);
++
++	return 0;
++}
++
++static void ipu6_isys_dwc_phy_aggr_setup(struct ipu6_isys *isys, u32 master,
++					 u32 slave, u32 mbps)
++{
++	/* Config mastermacro */
++	dwc_dphy_ifc_write_mask(isys, master, 0x133, 0x1, 0, 1);
++	dwc_dphy_ifc_write_mask(isys, slave, 0x133, 0x0, 0, 1);
++
++	/* Config master PHY clk lane to drive long channel clk */
++	dwc_dphy_ifc_write_mask(isys, master, 0x307, 0x1, 2, 1);
++	dwc_dphy_ifc_write_mask(isys, slave, 0x307, 0x0, 2, 1);
++
++	/* Config both PHYs data lanes to get clk from long channel */
++	dwc_dphy_ifc_write_mask(isys, master, 0x508, 0x1, 5, 1);
++	dwc_dphy_ifc_write_mask(isys, slave, 0x508, 0x1, 5, 1);
++	dwc_dphy_ifc_write_mask(isys, master, 0x708, 0x1, 5, 1);
++	dwc_dphy_ifc_write_mask(isys, slave, 0x708, 0x1, 5, 1);
++
++	/* Config slave PHY clk lane to bypass long channel clk to DDR clk */
++	dwc_dphy_ifc_write_mask(isys, master, 0x308, 0x0, 3, 1);
++	dwc_dphy_ifc_write_mask(isys, slave, 0x308, 0x1, 3, 1);
++
++	/* Override slave PHY clk lane enable (DPHYRXCLK_CLL_demux module) */
++	dwc_dphy_ifc_write_mask(isys, slave, 0xe0, 0x3, 0, 2);
++
++	/* Override slave PHY DDR clk lane enable (DPHYHSRX_div124 module) */
++	dwc_dphy_ifc_write_mask(isys, slave, 0xe1, 0x1, 1, 1);
++	dwc_dphy_ifc_write_mask(isys, slave, 0x307, 0x1, 3, 1);
++
++	/* Turn off slave PHY LP-RX clk lane */
++	dwc_dphy_ifc_write_mask(isys, slave, 0x304, 0x1, 7, 1);
++	dwc_dphy_ifc_write_mask(isys, slave, 0x305, 0xa, 0, 5);
++}
++
++#define PHY_E	4
++static int ipu6_isys_dwc_phy_powerup_ack(struct ipu6_isys *isys, u32 phy_id)
++{
++	struct device *dev = &isys->adev->auxdev.dev;
++	u32 rescal_done;
++	int ret;
++
++	ret = dwc_dphy_pwr_up(isys, phy_id);
++	if (ret != 0) {
++		dev_err(dev, "Dphy %u power up failed(%d)", phy_id, ret);
++		return ret;
++	}
++
++	/* reset forcerxmode */
++	dwc_dphy_write_mask(isys, phy_id, IPU6_DWC_DPHY_DFT_CTRL2, 0, 4, 1);
++	dwc_dphy_write_mask(isys, phy_id, IPU6_DWC_DPHY_DFT_CTRL2, 0, 8, 1);
++
++	dev_dbg(dev, "Dphy %u is ready!", phy_id);
++
++	if (phy_id != PHY_E || isys->phy_termcal_val)
++		return 0;
++
++	usleep_range(100, 200);
++	rescal_done = dwc_dphy_ifc_read_mask(isys, phy_id, 0x221, 7, 1);
++	if (rescal_done) {
++		isys->phy_termcal_val = dwc_dphy_ifc_read_mask(isys, phy_id,
++							       0x220, 2, 4);
++		dev_dbg(dev, "termcal done with value = %u",
++			isys->phy_termcal_val);
++	}
++
++	return 0;
++}
++
++static void ipu6_isys_dwc_phy_reset(struct ipu6_isys *isys, u32 phy_id)
++{
++	dev_dbg(&isys->adev->auxdev.dev, "Reset phy %u", phy_id);
++
++	dwc_dphy_write(isys, phy_id, IPU6_DWC_DPHY_SHUTDOWNZ, 0);
++	dwc_dphy_write(isys, phy_id, IPU6_DWC_DPHY_RSTZ, 0);
++	dwc_dphy_write(isys, phy_id, IPU6_DWC_DPHY_TEST_IFC_ACCESS_MODE,
++		       TEST_IFC_ACCESS_MODE_FSM);
++	dwc_dphy_write(isys, phy_id, IPU6_DWC_DPHY_TEST_IFC_REQ,
++		       TEST_IFC_REQ_RESET);
++}
++
++int ipu6_isys_dwc_phy_set_power(struct ipu6_isys *isys,
++				struct ipu6_isys_csi2_config *cfg,
++				const struct ipu6_isys_csi2_timing *timing,
++				bool on)
++{
++	struct device *dev = &isys->adev->auxdev.dev;
++	void __iomem *isys_base = isys->pdata->base;
++	u32 phy_id, primary, secondary;
++	u32 nlanes, port, mbps;
++	s64 link_freq;
++	int ret;
++
++	port = cfg->port;
++
++	if (!isys_base || port >= isys->pdata->ipdata->csi2.nports) {
++		dev_warn(dev, "invalid port ID %d\n", port);
++		return -EINVAL;
++	}
++
++	nlanes = cfg->nlanes;
++	/* only port 0, 2 and 4 support 4 lanes */
++	if (nlanes == 4 && port % 2) {
++		dev_err(dev, "invalid csi-port %u with %u lanes\n", port,
++			nlanes);
++		return -EINVAL;
++	}
++
++	link_freq = ipu6_isys_csi2_get_link_freq(&isys->csi2[port]);
++	if (link_freq < 0) {
++		dev_err(dev, "get link freq failed(%lld).\n", link_freq);
++		return link_freq;
++	}
++
++	mbps = div_u64(link_freq, 500000);
++
++	phy_id = port;
++	primary = port & ~1;
++	secondary = primary + 1;
++	if (on) {
++		if (nlanes == 4) {
++			dev_dbg(dev, "config phy %u and %u in aggr mode\n",
++				primary, secondary);
++
++			ipu6_isys_dwc_phy_reset(isys, primary);
++			ipu6_isys_dwc_phy_reset(isys, secondary);
++			ipu6_isys_dwc_phy_aggr_setup(isys, primary,
++						     secondary, mbps);
++
++			ret = ipu6_isys_dwc_phy_config(isys, primary, mbps);
++			if (ret)
++				return ret;
++			ret = ipu6_isys_dwc_phy_config(isys, secondary, mbps);
++			if (ret)
++				return ret;
++
++			ret = ipu6_isys_dwc_phy_powerup_ack(isys, primary);
++			if (ret)
++				return ret;
++
++			ret = ipu6_isys_dwc_phy_powerup_ack(isys, secondary);
++			return ret;
++		}
++
++		dev_dbg(dev, "config phy %u with %u lanes in non-aggr mode\n",
++			phy_id, nlanes);
++
++		ipu6_isys_dwc_phy_reset(isys, phy_id);
++		ret = ipu6_isys_dwc_phy_config(isys, phy_id, mbps);
++		if (ret)
++			return ret;
++
++		ret = ipu6_isys_dwc_phy_powerup_ack(isys, phy_id);
++		return ret;
++	}
++
++	if (nlanes == 4) {
++		dev_dbg(dev, "Power down phy %u and phy %u for port %u\n",
++			primary, secondary, port);
++		ipu6_isys_dwc_phy_reset(isys, secondary);
++		ipu6_isys_dwc_phy_reset(isys, primary);
++
++		return 0;
++	}
++
++	dev_dbg(dev, "Powerdown phy %u with %u lanes\n", phy_id, nlanes);
++
++	ipu6_isys_dwc_phy_reset(isys, phy_id);
++
++	return 0;
++}
+diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys-jsl-phy.c b/drivers/media/pci/intel/ipu6/ipu6-isys-jsl-phy.c
+new file mode 100644
+index 000000000000..dcc7743e0cee
+--- /dev/null
++++ b/drivers/media/pci/intel/ipu6/ipu6-isys-jsl-phy.c
+@@ -0,0 +1,242 @@
++// SPDX-License-Identifier: GPL-2.0-only
++/*
++ * Copyright (C) 2013 - 2023 Intel Corporation
++ */
++
++#include <linux/bitfield.h>
++#include <linux/bits.h>
++#include <linux/device.h>
++#include <linux/io.h>
++
++#include "ipu6-bus.h"
++#include "ipu6-isys.h"
++#include "ipu6-isys-csi2.h"
++#include "ipu6-platform-isys-csi2-reg.h"
++
++/* only use BB0, BB2, BB4, and BB6 on PHY0 */
++#define IPU6SE_ISYS_PHY_BB_NUM		4
++#define IPU6SE_ISYS_PHY_0_BASE		0x10000
++
++#define PHY_CPHY_DLL_OVRD(x)		(0x100 + 0x100 * (x))
++#define PHY_CPHY_RX_CONTROL1(x)		(0x110 + 0x100 * (x))
++#define PHY_DPHY_CFG(x)			(0x148 + 0x100 * (x))
++#define PHY_BB_AFE_CONFIG(x)		(0x174 + 0x100 * (x))
++
++/*
++ * use port_cfg to configure that which data lanes used
++ * +---------+     +------+ +-----+
++ * | port0 x4<-----|      | |     |
++ * |         |     | port | |     |
++ * | port1 x2<-----|      | |     |
++ * |         |     |      <-| PHY |
++ * | port2 x4<-----|      | |     |
++ * |         |     |config| |     |
++ * | port3 x2<-----|      | |     |
++ * +---------+     +------+ +-----+
++ */
++static const unsigned int csi2_port_cfg[][3] = {
++	{0, 0, 0x1f}, /* no link */
++	{4, 0, 0x10}, /* x4 + x4 config */
++	{2, 0, 0x12}, /* x2 + x2 config */
++	{1, 0, 0x13}, /* x1 + x1 config */
++	{2, 1, 0x15}, /* x2x1 + x2x1 config */
++	{1, 1, 0x16}, /* x1x1 + x1x1 config */
++	{2, 2, 0x18}, /* x2x2 + x2x2 config */
++	{1, 2, 0x19} /* x1x2 + x1x2 config */
++};
++
++/* port, nlanes, bbindex, portcfg */
++static const unsigned int phy_port_cfg[][4] = {
++	/* sip0 */
++	{0, 1, 0, 0x15},
++	{0, 2, 0, 0x15},
++	{0, 4, 0, 0x15},
++	{0, 4, 2, 0x22},
++	/* sip1 */
++	{2, 1, 4, 0x15},
++	{2, 2, 4, 0x15},
++	{2, 4, 4, 0x15},
++	{2, 4, 6, 0x22}
++};
++
++static void ipu6_isys_csi2_phy_config_by_port(struct ipu6_isys *isys,
++					      unsigned int port,
++					      unsigned int nlanes)
++{
++	struct device *dev = &isys->adev->auxdev.dev;
++	void __iomem *base = isys->adev->isp->base;
++	unsigned int bbnum;
++	u32 val, reg, i;
++
++	dev_dbg(dev, "port %u with %u lanes", port, nlanes);
++
++	/* only support <1.5Gbps */
++	for (i = 0; i < IPU6SE_ISYS_PHY_BB_NUM; i++) {
++		/* cphy_dll_ovrd.crcdc_fsm_dlane0 = 13 */
++		reg = IPU6SE_ISYS_PHY_0_BASE + PHY_CPHY_DLL_OVRD(i);
++		val = readl(base + reg);
++		val |= FIELD_PREP(GENMASK(6, 1), 13);
++		writel(val, base + reg);
++
++		/* cphy_rx_control1.en_crc1 = 1 */
++		reg = IPU6SE_ISYS_PHY_0_BASE + PHY_CPHY_RX_CONTROL1(i);
++		val = readl(base + reg);
++		val |= BIT(31);
++		writel(val, base + reg);
++
++		/* dphy_cfg.reserved = 1, .lden_from_dll_ovrd_0 = 1 */
++		reg = IPU6SE_ISYS_PHY_0_BASE + PHY_DPHY_CFG(i);
++		val = readl(base + reg);
++		val |= BIT(25) | BIT(26);
++		writel(val, base + reg);
++
++		/* cphy_dll_ovrd.lden_crcdc_fsm_dlane0 = 1 */
++		reg = IPU6SE_ISYS_PHY_0_BASE + PHY_CPHY_DLL_OVRD(i);
++		val = readl(base + reg);
++		val |= BIT(0);
++		writel(val, base + reg);
++	}
++
++	/* Front end config, use minimal channel loss */
++	for (i = 0; i < ARRAY_SIZE(phy_port_cfg); i++) {
++		if (phy_port_cfg[i][0] == port &&
++		    phy_port_cfg[i][1] == nlanes) {
++			bbnum = phy_port_cfg[i][2] / 2;
++			reg = IPU6SE_ISYS_PHY_0_BASE + PHY_BB_AFE_CONFIG(bbnum);
++			val = readl(base + reg);
++			val |= phy_port_cfg[i][3];
++			writel(val, base + reg);
++		}
++	}
++}
++
++static void ipu6_isys_csi2_rx_control(struct ipu6_isys *isys)
++{
++	void __iomem *base = isys->adev->isp->base;
++	u32 val, reg;
++
++	reg = CSI2_HUB_GPREG_SIP0_CSI_RX_A_CONTROL;
++	val = readl(base + reg);
++	val |= BIT(0);
++	writel(val, base + CSI2_HUB_GPREG_SIP0_CSI_RX_A_CONTROL);
++
++	reg = CSI2_HUB_GPREG_SIP0_CSI_RX_B_CONTROL;
++	val = readl(base + reg);
++	val |= BIT(0);
++	writel(val, base + CSI2_HUB_GPREG_SIP0_CSI_RX_B_CONTROL);
++
++	reg = CSI2_HUB_GPREG_SIP1_CSI_RX_A_CONTROL;
++	val = readl(base + reg);
++	val |= BIT(0);
++	writel(val, base + CSI2_HUB_GPREG_SIP1_CSI_RX_A_CONTROL);
++
++	reg = CSI2_HUB_GPREG_SIP1_CSI_RX_B_CONTROL;
++	val = readl(base + reg);
++	val |= BIT(0);
++	writel(val, base + CSI2_HUB_GPREG_SIP1_CSI_RX_B_CONTROL);
++}
++
++static int ipu6_isys_csi2_set_port_cfg(struct ipu6_isys *isys,
++				       unsigned int port, unsigned int nlanes)
++{
++	struct device *dev = &isys->adev->auxdev.dev;
++	unsigned int sip = port / 2;
++	unsigned int index;
++
++	switch (nlanes) {
++	case 1:
++		index = 5;
++		break;
++	case 2:
++		index = 6;
++		break;
++	case 4:
++		index = 1;
++		break;
++	default:
++		dev_err(dev, "lanes nr %u is unsupported\n", nlanes);
++		return -EINVAL;
++	}
++
++	dev_dbg(dev, "port config for port %u with %u lanes\n",	port, nlanes);
++
++	writel(csi2_port_cfg[index][2],
++	       isys->pdata->base + CSI2_HUB_GPREG_SIP_FB_PORT_CFG(sip));
++
++	return 0;
++}
++
++static void
++ipu6_isys_csi2_set_timing(struct ipu6_isys *isys,
++			  const struct ipu6_isys_csi2_timing *timing,
++			  unsigned int port, unsigned int nlanes)
++{
++	struct device *dev = &isys->adev->auxdev.dev;
++	void __iomem *reg;
++	u32 port_base;
++	u32 i;
++
++	port_base = (port % 2) ? CSI2_SIP_TOP_CSI_RX_PORT_BASE_1(port) :
++		CSI2_SIP_TOP_CSI_RX_PORT_BASE_0(port);
++
++	dev_dbg(dev, "set timing for port %u with %u lanes\n", port, nlanes);
++
++	reg = isys->pdata->base + port_base;
++	reg += CSI2_SIP_TOP_CSI_RX_DLY_CNT_TERMEN_CLANE;
++
++	writel(timing->ctermen, reg);
++
++	reg = isys->pdata->base + port_base;
++	reg += CSI2_SIP_TOP_CSI_RX_DLY_CNT_SETTLE_CLANE;
++	writel(timing->csettle, reg);
++
++	for (i = 0; i < nlanes; i++) {
++		reg = isys->pdata->base + port_base;
++		reg += CSI2_SIP_TOP_CSI_RX_DLY_CNT_TERMEN_DLANE(i);
++		writel(timing->dtermen, reg);
++
++		reg = isys->pdata->base + port_base;
++		reg += CSI2_SIP_TOP_CSI_RX_DLY_CNT_SETTLE_DLANE(i);
++		writel(timing->dsettle, reg);
++	}
++}
++
++#define DPHY_TIMER_INCR	0x28
++int ipu6_isys_jsl_phy_set_power(struct ipu6_isys *isys,
++				struct ipu6_isys_csi2_config *cfg,
++				const struct ipu6_isys_csi2_timing *timing,
++				bool on)
++{
++	struct device *dev = &isys->adev->auxdev.dev;
++	void __iomem *isys_base = isys->pdata->base;
++	int ret = 0;
++	u32 nlanes;
++	u32 port;
++
++	if (!on)
++		return 0;
++
++	port = cfg->port;
++	nlanes = cfg->nlanes;
++
++	if (!isys_base || port >= isys->pdata->ipdata->csi2.nports) {
++		dev_warn(dev, "invalid port ID %d\n", port);
++		return -EINVAL;
++	}
++
++	ipu6_isys_csi2_phy_config_by_port(isys, port, nlanes);
++
++	writel(DPHY_TIMER_INCR,
++	       isys->pdata->base + CSI2_HUB_GPREG_DPHY_TIMER_INCR);
++
++	/* set port cfg and rx timing */
++	ipu6_isys_csi2_set_timing(isys, timing, port, nlanes);
++
++	ret = ipu6_isys_csi2_set_port_cfg(isys, port, nlanes);
++	if (ret)
++		return ret;
++
++	ipu6_isys_csi2_rx_control(isys);
++
++	return 0;
++}
+diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys-mcd-phy.c b/drivers/media/pci/intel/ipu6/ipu6-isys-mcd-phy.c
+new file mode 100644
+index 000000000000..9abf389a05f1
+--- /dev/null
++++ b/drivers/media/pci/intel/ipu6/ipu6-isys-mcd-phy.c
+@@ -0,0 +1,720 @@
++// SPDX-License-Identifier: GPL-2.0-only
++/*
++ * Copyright (C) 2013 - 2023 Intel Corporation
++ */
++
++#include <linux/bits.h>
++#include <linux/container_of.h>
++#include <linux/device.h>
++#include <linux/iopoll.h>
++#include <linux/list.h>
++#include <linux/refcount.h>
++#include <linux/time64.h>
++
++#include <media/v4l2-async.h>
++
++#include "ipu6.h"
++#include "ipu6-bus.h"
++#include "ipu6-isys.h"
++#include "ipu6-isys-csi2.h"
++#include "ipu6-platform-isys-csi2-reg.h"
++
++#define CSI_REG_HUB_GPREG_PHY_CTL(id) (CSI_REG_BASE + 0x18008 + (id) * 0x8)
++#define CSI_REG_HUB_GPREG_PHY_CTL_RESET			BIT(4)
++#define CSI_REG_HUB_GPREG_PHY_CTL_PWR_EN		BIT(0)
++#define CSI_REG_HUB_GPREG_PHY_STATUS(id) (CSI_REG_BASE + 0x1800c + (id) * 0x8)
++#define CSI_REG_HUB_GPREG_PHY_POWER_ACK			BIT(0)
++#define CSI_REG_HUB_GPREG_PHY_READY			BIT(4)
++
++#define MCD_PHY_POWER_STATUS_TIMEOUT			(200 * USEC_PER_MSEC)
++
++/*
++ * bridge to phy in buttress reg map, each phy has 16 kbytes
++ * only 2 phys for TGL U and Y
++ */
++#define IPU6_ISYS_MCD_PHY_BASE(i)			(0x10000 + (i) * 0x4000)
++
++/*
++ *  There are 2 MCD DPHY instances on TGL and 1 MCD DPHY instance on ADL.
++ *  Each MCD PHY has 12-lanes which has 8 data lanes and 4 clock lanes.
++ *  CSI port 1, 3 (5, 7) can support max 2 data lanes.
++ *  CSI port 0, 2 (4, 6) can support max 4 data lanes.
++ *  PHY configurations are PPI based instead of port.
++ *  Left:
++ *  +---------+---------+---------+---------+--------+---------+----------+
++ *  |         |         |         |         |        |         |          |
++ *  | PPI     | PPI5    | PPI4    | PPI3    | PPI2   | PPI1    | PPI0     |
++ *  +---------+---------+---------+---------+--------+---------+----------+
++ *  |         |         |         |         |        |         |          |
++ *  | x4      | unused  | D3      | D2      | C0     | D0      | D1       |
++ *  |---------+---------+---------+---------+--------+---------+----------+
++ *  |         |         |         |         |        |         |          |
++ *  | x2x2    | C1      | D0      | D1      | C0     | D0      | D1       |
++ *  ----------+---------+---------+---------+--------+---------+----------+
++ *  |         |         |         |         |        |         |          |
++ *  | x2x1    | C1      | D0      | unused  | C0     | D0      | D1       |
++ *  +---------+---------+---------+---------+--------+---------+----------+
++ *  |         |         |         |         |        |         |          |
++ *  | x1x1    | C1      | D0      | unused  | C0     | D0      | unused   |
++ *  +---------+---------+---------+---------+--------+---------+----------+
++ *  |         |         |         |         |        |         |          |
++ *  | x1x2    | C1      | D0      | D1      | C0     | D0      | unused   |
++ *  +---------+---------+---------+---------+--------+---------+----------+
++ *
++ *  Right:
++ *  +---------+---------+---------+---------+--------+---------+----------+
++ *  |         |         |         |         |        |         |          |
++ *  | PPI     | PPI6    | PPI7    | PPI8    | PPI9   | PPI10   | PPI11    |
++ *  +---------+---------+---------+---------+--------+---------+----------+
++ *  |         |         |         |         |        |         |          |
++ *  | x4      | D1      | D0      | C2      | D2     | D3      | unused   |
++ *  |---------+---------+---------+---------+--------+---------+----------+
++ *  |         |         |         |         |        |         |          |
++ *  | x2x2    | D1      | D0      | C2      | D1     | D0      | C3       |
++ *  ----------+---------+---------+---------+--------+---------+----------+
++ *  |         |         |         |         |        |         |          |
++ *  | x2x1    | D1      | D0      | C2      | unused | D0      | C3       |
++ *  +---------+---------+---------+---------+--------+---------+----------+
++ *  |         |         |         |         |        |         |          |
++ *  | x1x1    | unused  | D0      | C2      | unused | D0      | C3       |
++ *  +---------+---------+---------+---------+--------+---------+----------+
++ *  |         |         |         |         |        |         |          |
++ *  | x1x2    | unused  | D0      | C2      | D1     | D0      | C3       |
++ *  +---------+---------+---------+---------+--------+---------+----------+
++ *
++ * ppi mapping per phy :
++ *
++ * x4 + x4:
++ * Left : port0 - PPI range {0, 1, 2, 3, 4}
++ * Right: port2 - PPI range {6, 7, 8, 9, 10}
++ *
++ * x4 + x2x2:
++ * Left: port0 - PPI range {0, 1, 2, 3, 4}
++ * Right: port2 - PPI range {6, 7, 8}, port3 - PPI range {9, 10, 11}
++ *
++ * x2x2 + x4:
++ * Left: port0 - PPI range {0, 1, 2}, port1 - PPI range {3, 4, 5}
++ * Right: port2 - PPI range {6, 7, 8, 9, 10}
++ *
++ * x2x2 + x2x2:
++ * Left : port0 - PPI range {0, 1, 2}, port1 - PPI range {3, 4, 5}
++ * Right: port2 - PPI range {6, 7, 8}, port3 - PPI range {9, 10, 11}
++ */
++
++struct phy_reg {
++	u32 reg;
++	u32 val;
++};
++
++static const struct phy_reg common_init_regs[] = {
++	/* for TGL-U, use 0x80000000 */
++	{0x00000040, 0x80000000},
++	{0x00000044, 0x00a80880},
++	{0x00000044, 0x00b80880},
++	{0x00000010, 0x0000078c},
++	{0x00000344, 0x2f4401e2},
++	{0x00000544, 0x924401e2},
++	{0x00000744, 0x594401e2},
++	{0x00000944, 0x624401e2},
++	{0x00000b44, 0xfc4401e2},
++	{0x00000d44, 0xc54401e2},
++	{0x00000f44, 0x034401e2},
++	{0x00001144, 0x8f4401e2},
++	{0x00001344, 0x754401e2},
++	{0x00001544, 0xe94401e2},
++	{0x00001744, 0xcb4401e2},
++	{0x00001944, 0xfa4401e2}
++};
++
++static const struct phy_reg x1_port0_config_regs[] = {
++	{0x00000694, 0xc80060fa},
++	{0x00000680, 0x3d4f78ea},
++	{0x00000690, 0x10a0140b},
++	{0x000006a8, 0xdf04010a},
++	{0x00000700, 0x57050060},
++	{0x00000710, 0x0030001c},
++	{0x00000738, 0x5f004444},
++	{0x0000073c, 0x78464204},
++	{0x00000748, 0x7821f940},
++	{0x0000074c, 0xb2000433},
++	{0x00000494, 0xfe6030fa},
++	{0x00000480, 0x29ef5ed0},
++	{0x00000490, 0x10a0540b},
++	{0x000004a8, 0x7a01010a},
++	{0x00000500, 0xef053460},
++	{0x00000510, 0xe030101c},
++	{0x00000538, 0xdf808444},
++	{0x0000053c, 0xc8422204},
++	{0x00000540, 0x0180088c},
++	{0x00000574, 0x00000000},
++	{0x00000000, 0x00000000}
++};
++
++static const struct phy_reg x1_port1_config_regs[] = {
++	{0x00000c94, 0xc80060fa},
++	{0x00000c80, 0xcf47abea},
++	{0x00000c90, 0x10a0840b},
++	{0x00000ca8, 0xdf04010a},
++	{0x00000d00, 0x57050060},
++	{0x00000d10, 0x0030001c},
++	{0x00000d38, 0x5f004444},
++	{0x00000d3c, 0x78464204},
++	{0x00000d48, 0x7821f940},
++	{0x00000d4c, 0xb2000433},
++	{0x00000a94, 0xc91030fa},
++	{0x00000a80, 0x5a166ed0},
++	{0x00000a90, 0x10a0540b},
++	{0x00000aa8, 0x5d060100},
++	{0x00000b00, 0xef053460},
++	{0x00000b10, 0xa030101c},
++	{0x00000b38, 0xdf808444},
++	{0x00000b3c, 0xc8422204},
++	{0x00000b40, 0x0180088c},
++	{0x00000b74, 0x00000000},
++	{0x00000000, 0x00000000}
++};
++
++static const struct phy_reg x1_port2_config_regs[] = {
++	{0x00001294, 0x28f000fa},
++	{0x00001280, 0x08130cea},
++	{0x00001290, 0x10a0140b},
++	{0x000012a8, 0xd704010a},
++	{0x00001300, 0x8d050060},
++	{0x00001310, 0x0030001c},
++	{0x00001338, 0xdf008444},
++	{0x0000133c, 0x78422204},
++	{0x00001348, 0x7821f940},
++	{0x0000134c, 0x5a000433},
++	{0x00001094, 0x2d20b0fa},
++	{0x00001080, 0xade75dd0},
++	{0x00001090, 0x10a0540b},
++	{0x000010a8, 0xb101010a},
++	{0x00001100, 0x33053460},
++	{0x00001110, 0x0030101c},
++	{0x00001138, 0xdf808444},
++	{0x0000113c, 0xc8422204},
++	{0x00001140, 0x8180088c},
++	{0x00001174, 0x00000000},
++	{0x00000000, 0x00000000}
++};
++
++static const struct phy_reg x1_port3_config_regs[] = {
++	{0x00001894, 0xc80060fa},
++	{0x00001880, 0x0f90fd6a},
++	{0x00001890, 0x10a0840b},
++	{0x000018a8, 0xdf04010a},
++	{0x00001900, 0x57050060},
++	{0x00001910, 0x0030001c},
++	{0x00001938, 0x5f004444},
++	{0x0000193c, 0x78464204},
++	{0x00001948, 0x7821f940},
++	{0x0000194c, 0xb2000433},
++	{0x00001694, 0x3050d0fa},
++	{0x00001680, 0x0ef6d050},
++	{0x00001690, 0x10a0540b},
++	{0x000016a8, 0xe301010a},
++	{0x00001700, 0x69053460},
++	{0x00001710, 0xa030101c},
++	{0x00001738, 0xdf808444},
++	{0x0000173c, 0xc8422204},
++	{0x00001740, 0x0180088c},
++	{0x00001774, 0x00000000},
++	{0x00000000, 0x00000000}
++};
++
++static const struct phy_reg x2_port0_config_regs[] = {
++	{0x00000694, 0xc80060fa},
++	{0x00000680, 0x3d4f78ea},
++	{0x00000690, 0x10a0140b},
++	{0x000006a8, 0xdf04010a},
++	{0x00000700, 0x57050060},
++	{0x00000710, 0x0030001c},
++	{0x00000738, 0x5f004444},
++	{0x0000073c, 0x78464204},
++	{0x00000748, 0x7821f940},
++	{0x0000074c, 0xb2000433},
++	{0x00000494, 0xc80060fa},
++	{0x00000480, 0x29ef5ed8},
++	{0x00000490, 0x10a0540b},
++	{0x000004a8, 0x7a01010a},
++	{0x00000500, 0xef053460},
++	{0x00000510, 0xe030101c},
++	{0x00000538, 0xdf808444},
++	{0x0000053c, 0xc8422204},
++	{0x00000540, 0x0180088c},
++	{0x00000574, 0x00000000},
++	{0x00000294, 0xc80060fa},
++	{0x00000280, 0xcb45b950},
++	{0x00000290, 0x10a0540b},
++	{0x000002a8, 0x8c01010a},
++	{0x00000300, 0xef053460},
++	{0x00000310, 0x8030101c},
++	{0x00000338, 0x41808444},
++	{0x0000033c, 0x32422204},
++	{0x00000340, 0x0180088c},
++	{0x00000374, 0x00000000},
++	{0x00000000, 0x00000000}
++};
++
++static const struct phy_reg x2_port1_config_regs[] = {
++	{0x00000c94, 0xc80060fa},
++	{0x00000c80, 0xcf47abea},
++	{0x00000c90, 0x10a0840b},
++	{0x00000ca8, 0xdf04010a},
++	{0x00000d00, 0x57050060},
++	{0x00000d10, 0x0030001c},
++	{0x00000d38, 0x5f004444},
++	{0x00000d3c, 0x78464204},
++	{0x00000d48, 0x7821f940},
++	{0x00000d4c, 0xb2000433},
++	{0x00000a94, 0xc80060fa},
++	{0x00000a80, 0x5a166ed8},
++	{0x00000a90, 0x10a0540b},
++	{0x00000aa8, 0x7a01010a},
++	{0x00000b00, 0xef053460},
++	{0x00000b10, 0xa030101c},
++	{0x00000b38, 0xdf808444},
++	{0x00000b3c, 0xc8422204},
++	{0x00000b40, 0x0180088c},
++	{0x00000b74, 0x00000000},
++	{0x00000894, 0xc80060fa},
++	{0x00000880, 0x4d4f21d0},
++	{0x00000890, 0x10a0540b},
++	{0x000008a8, 0x5601010a},
++	{0x00000900, 0xef053460},
++	{0x00000910, 0x8030101c},
++	{0x00000938, 0xdf808444},
++	{0x0000093c, 0xc8422204},
++	{0x00000940, 0x0180088c},
++	{0x00000974, 0x00000000},
++	{0x00000000, 0x00000000}
++};
++
++static const struct phy_reg x2_port2_config_regs[] = {
++	{0x00001294, 0xc80060fa},
++	{0x00001280, 0x08130cea},
++	{0x00001290, 0x10a0140b},
++	{0x000012a8, 0xd704010a},
++	{0x00001300, 0x8d050060},
++	{0x00001310, 0x0030001c},
++	{0x00001338, 0xdf008444},
++	{0x0000133c, 0x78422204},
++	{0x00001348, 0x7821f940},
++	{0x0000134c, 0x5a000433},
++	{0x00001094, 0xc80060fa},
++	{0x00001080, 0xade75dd8},
++	{0x00001090, 0x10a0540b},
++	{0x000010a8, 0xb101010a},
++	{0x00001100, 0x33053460},
++	{0x00001110, 0x0030101c},
++	{0x00001138, 0xdf808444},
++	{0x0000113c, 0xc8422204},
++	{0x00001140, 0x8180088c},
++	{0x00001174, 0x00000000},
++	{0x00000e94, 0xc80060fa},
++	{0x00000e80, 0x0fbf16d0},
++	{0x00000e90, 0x10a0540b},
++	{0x00000ea8, 0x7a01010a},
++	{0x00000f00, 0xf5053460},
++	{0x00000f10, 0xc030101c},
++	{0x00000f38, 0xdf808444},
++	{0x00000f3c, 0xc8422204},
++	{0x00000f40, 0x8180088c},
++	{0x00000000, 0x00000000}
++};
++
++static const struct phy_reg x2_port3_config_regs[] = {
++	{0x00001894, 0xc80060fa},
++	{0x00001880, 0x0f90fd6a},
++	{0x00001890, 0x10a0840b},
++	{0x000018a8, 0xdf04010a},
++	{0x00001900, 0x57050060},
++	{0x00001910, 0x0030001c},
++	{0x00001938, 0x5f004444},
++	{0x0000193c, 0x78464204},
++	{0x00001948, 0x7821f940},
++	{0x0000194c, 0xb2000433},
++	{0x00001694, 0xc80060fa},
++	{0x00001680, 0x0ef6d058},
++	{0x00001690, 0x10a0540b},
++	{0x000016a8, 0x7a01010a},
++	{0x00001700, 0x69053460},
++	{0x00001710, 0xa030101c},
++	{0x00001738, 0xdf808444},
++	{0x0000173c, 0xc8422204},
++	{0x00001740, 0x0180088c},
++	{0x00001774, 0x00000000},
++	{0x00001494, 0xc80060fa},
++	{0x00001480, 0xf9d34bd0},
++	{0x00001490, 0x10a0540b},
++	{0x000014a8, 0x7a01010a},
++	{0x00001500, 0x1b053460},
++	{0x00001510, 0x0030101c},
++	{0x00001538, 0xdf808444},
++	{0x0000153c, 0xc8422204},
++	{0x00001540, 0x8180088c},
++	{0x00001574, 0x00000000},
++	{0x00000000, 0x00000000}
++};
++
++static const struct phy_reg x4_port0_config_regs[] = {
++	{0x00000694, 0xc80060fa},
++	{0x00000680, 0x3d4f78fa},
++	{0x00000690, 0x10a0140b},
++	{0x000006a8, 0xdf04010a},
++	{0x00000700, 0x57050060},
++	{0x00000710, 0x0030001c},
++	{0x00000738, 0x5f004444},
++	{0x0000073c, 0x78464204},
++	{0x00000748, 0x7821f940},
++	{0x0000074c, 0xb2000433},
++	{0x00000494, 0xfe6030fa},
++	{0x00000480, 0x29ef5ed8},
++	{0x00000490, 0x10a0540b},
++	{0x000004a8, 0x7a01010a},
++	{0x00000500, 0xef053460},
++	{0x00000510, 0xe030101c},
++	{0x00000538, 0xdf808444},
++	{0x0000053c, 0xc8422204},
++	{0x00000540, 0x0180088c},
++	{0x00000574, 0x00000004},
++	{0x00000294, 0x23e030fa},
++	{0x00000280, 0xcb45b950},
++	{0x00000290, 0x10a0540b},
++	{0x000002a8, 0x8c01010a},
++	{0x00000300, 0xef053460},
++	{0x00000310, 0x8030101c},
++	{0x00000338, 0x41808444},
++	{0x0000033c, 0x32422204},
++	{0x00000340, 0x0180088c},
++	{0x00000374, 0x00000004},
++	{0x00000894, 0x5620b0fa},
++	{0x00000880, 0x4d4f21dc},
++	{0x00000890, 0x10a0540b},
++	{0x000008a8, 0x5601010a},
++	{0x00000900, 0xef053460},
++	{0x00000910, 0x8030101c},
++	{0x00000938, 0xdf808444},
++	{0x0000093c, 0xc8422204},
++	{0x00000940, 0x0180088c},
++	{0x00000974, 0x00000004},
++	{0x00000a94, 0xc91030fa},
++	{0x00000a80, 0x5a166ecc},
++	{0x00000a90, 0x10a0540b},
++	{0x00000aa8, 0x5d01010a},
++	{0x00000b00, 0xef053460},
++	{0x00000b10, 0xa030101c},
++	{0x00000b38, 0xdf808444},
++	{0x00000b3c, 0xc8422204},
++	{0x00000b40, 0x0180088c},
++	{0x00000b74, 0x00000004},
++	{0x00000000, 0x00000000}
++};
++
++static const struct phy_reg x4_port1_config_regs[] = {
++	{0x00000000, 0x00000000}
++};
++
++static const struct phy_reg x4_port2_config_regs[] = {
++	{0x00001294, 0x28f000fa},
++	{0x00001280, 0x08130cfa},
++	{0x00001290, 0x10c0140b},
++	{0x000012a8, 0xd704010a},
++	{0x00001300, 0x8d050060},
++	{0x00001310, 0x0030001c},
++	{0x00001338, 0xdf008444},
++	{0x0000133c, 0x78422204},
++	{0x00001348, 0x7821f940},
++	{0x0000134c, 0x5a000433},
++	{0x00001094, 0x2d20b0fa},
++	{0x00001080, 0xade75dd8},
++	{0x00001090, 0x10a0540b},
++	{0x000010a8, 0xb101010a},
++	{0x00001100, 0x33053460},
++	{0x00001110, 0x0030101c},
++	{0x00001138, 0xdf808444},
++	{0x0000113c, 0xc8422204},
++	{0x00001140, 0x8180088c},
++	{0x00001174, 0x00000004},
++	{0x00000e94, 0xd308d0fa},
++	{0x00000e80, 0x0fbf16d0},
++	{0x00000e90, 0x10a0540b},
++	{0x00000ea8, 0x2c01010a},
++	{0x00000f00, 0xf5053460},
++	{0x00000f10, 0xc030101c},
++	{0x00000f38, 0xdf808444},
++	{0x00000f3c, 0xc8422204},
++	{0x00000f40, 0x8180088c},
++	{0x00000f74, 0x00000004},
++	{0x00001494, 0x136850fa},
++	{0x00001480, 0xf9d34bdc},
++	{0x00001490, 0x10a0540b},
++	{0x000014a8, 0x5a01010a},
++	{0x00001500, 0x1b053460},
++	{0x00001510, 0x0030101c},
++	{0x00001538, 0xdf808444},
++	{0x0000153c, 0xc8422204},
++	{0x00001540, 0x8180088c},
++	{0x00001574, 0x00000004},
++	{0x00001694, 0x3050d0fa},
++	{0x00001680, 0x0ef6d04c},
++	{0x00001690, 0x10a0540b},
++	{0x000016a8, 0xe301010a},
++	{0x00001700, 0x69053460},
++	{0x00001710, 0xa030101c},
++	{0x00001738, 0xdf808444},
++	{0x0000173c, 0xc8422204},
++	{0x00001740, 0x0180088c},
++	{0x00001774, 0x00000004},
++	{0x00000000, 0x00000000}
++};
++
++static const struct phy_reg x4_port3_config_regs[] = {
++	{0x00000000, 0x00000000}
++};
++
++static const struct phy_reg *x1_config_regs[4] = {
++	x1_port0_config_regs,
++	x1_port1_config_regs,
++	x1_port2_config_regs,
++	x1_port3_config_regs
++};
++
++static const struct phy_reg *x2_config_regs[4] = {
++	x2_port0_config_regs,
++	x2_port1_config_regs,
++	x2_port2_config_regs,
++	x2_port3_config_regs
++};
++
++static const struct phy_reg *x4_config_regs[4] = {
++	x4_port0_config_regs,
++	x4_port1_config_regs,
++	x4_port2_config_regs,
++	x4_port3_config_regs
++};
++
++static const struct phy_reg **config_regs[3] = {
++	x1_config_regs,
++	x2_config_regs,
++	x4_config_regs
++};
++
++static int ipu6_isys_mcd_phy_powerup_ack(struct ipu6_isys *isys, u8 id)
++{
++	struct device *dev = &isys->adev->auxdev.dev;
++	void __iomem *isys_base = isys->pdata->base;
++	u32 val;
++	int ret;
++
++	val = readl(isys_base + CSI_REG_HUB_GPREG_PHY_CTL(id));
++	val |= CSI_REG_HUB_GPREG_PHY_CTL_PWR_EN;
++	writel(val, isys_base + CSI_REG_HUB_GPREG_PHY_CTL(id));
++
++	ret = readl_poll_timeout(isys_base + CSI_REG_HUB_GPREG_PHY_STATUS(id),
++				 val, val & CSI_REG_HUB_GPREG_PHY_POWER_ACK,
++				 200, MCD_PHY_POWER_STATUS_TIMEOUT);
++	if (ret)
++		dev_err(dev, "PHY%d powerup ack timeout", id);
++
++	return ret;
++}
++
++static int ipu6_isys_mcd_phy_powerdown_ack(struct ipu6_isys *isys, u8 id)
++{
++	struct device *dev = &isys->adev->auxdev.dev;
++	void __iomem *isys_base = isys->pdata->base;
++	u32 val;
++	int ret;
++
++	writel(0, isys_base + CSI_REG_HUB_GPREG_PHY_CTL(id));
++	ret = readl_poll_timeout(isys_base + CSI_REG_HUB_GPREG_PHY_STATUS(id),
++				 val, !(val & CSI_REG_HUB_GPREG_PHY_POWER_ACK),
++				 200, MCD_PHY_POWER_STATUS_TIMEOUT);
++	if (ret)
++		dev_err(dev, "PHY%d powerdown ack timeout", id);
++
++	return ret;
++}
++
++static void ipu6_isys_mcd_phy_reset(struct ipu6_isys *isys, u8 id, bool assert)
++{
++	void __iomem *isys_base = isys->pdata->base;
++	u32 val;
++
++	val = readl(isys_base + CSI_REG_HUB_GPREG_PHY_CTL(id));
++	if (assert)
++		val |= CSI_REG_HUB_GPREG_PHY_CTL_RESET;
++	else
++		val &= ~(CSI_REG_HUB_GPREG_PHY_CTL_RESET);
++
++	writel(val, isys_base + CSI_REG_HUB_GPREG_PHY_CTL(id));
++}
++
++static int ipu6_isys_mcd_phy_ready(struct ipu6_isys *isys, u8 id)
++{
++	struct device *dev = &isys->adev->auxdev.dev;
++	void __iomem *isys_base = isys->pdata->base;
++	u32 val;
++	int ret;
++
++	ret = readl_poll_timeout(isys_base + CSI_REG_HUB_GPREG_PHY_STATUS(id),
++				 val, val & CSI_REG_HUB_GPREG_PHY_READY,
++				 200, MCD_PHY_POWER_STATUS_TIMEOUT);
++	if (ret)
++		dev_err(dev, "PHY%d ready ack timeout", id);
++
++	return ret;
++}
++
++static void ipu6_isys_mcd_phy_common_init(struct ipu6_isys *isys)
++{
++	struct ipu6_bus_device *adev = isys->adev;
++	struct ipu6_device *isp = adev->isp;
++	void __iomem *isp_base = isp->base;
++	struct sensor_async_sd *s_asd;
++	struct v4l2_async_connection *asc;
++	void __iomem *phy_base;
++	unsigned int i;
++	u8 phy_id;
++
++	list_for_each_entry(asc, &isys->notifier.done_list, asc_entry) {
++		s_asd = container_of(asc, struct sensor_async_sd, asc);
++		phy_id = s_asd->csi2.port / 4;
++		phy_base = isp_base + IPU6_ISYS_MCD_PHY_BASE(phy_id);
++
++		for (i = 0; i < ARRAY_SIZE(common_init_regs); i++)
++			writel(common_init_regs[i].val,
++			       phy_base + common_init_regs[i].reg);
++	}
++}
++
++static int ipu6_isys_driver_port_to_phy_port(struct ipu6_isys_csi2_config *cfg)
++{
++	int phy_port;
++	int ret;
++
++	if (!(cfg->nlanes == 4 || cfg->nlanes == 2 || cfg->nlanes == 1))
++		return -EINVAL;
++
++	/* B,F -> C0 A,E -> C1 C,G -> C2 D,H -> C4 */
++	/* normalize driver port number */
++	phy_port = cfg->port % 4;
++
++	/* swap port number only for A and B */
++	if (phy_port == 0)
++		phy_port = 1;
++	else if (phy_port == 1)
++		phy_port = 0;
++
++	ret = phy_port;
++
++	/* check validity per lane configuration */
++	if (cfg->nlanes == 4 && !(phy_port == 0 || phy_port == 2))
++		ret = -EINVAL;
++	else if ((cfg->nlanes == 2 || cfg->nlanes == 1) &&
++		 !(phy_port >= 0 && phy_port <= 3))
++		ret = -EINVAL;
++
++	return ret;
++}
++
++static int ipu6_isys_mcd_phy_config(struct ipu6_isys *isys)
++{
++	struct device *dev = &isys->adev->auxdev.dev;
++	struct ipu6_bus_device *adev = isys->adev;
++	const struct phy_reg **phy_config_regs;
++	struct ipu6_device *isp = adev->isp;
++	void __iomem *isp_base = isp->base;
++	struct sensor_async_sd *s_asd;
++	struct ipu6_isys_csi2_config cfg;
++	struct v4l2_async_connection *asc;
++	u8 phy_port, phy_id;
++	unsigned int i;
++	void __iomem *phy_base;
++
++	list_for_each_entry(asc, &isys->notifier.done_list, asc_entry) {
++		s_asd = container_of(asc, struct sensor_async_sd, asc);
++		cfg.port = s_asd->csi2.port;
++		cfg.nlanes = s_asd->csi2.nlanes;
++		phy_port = ipu6_isys_driver_port_to_phy_port(&cfg);
++		if (phy_port < 0) {
++			dev_err(dev, "invalid port %d for lane %d", cfg.port,
++				cfg.nlanes);
++			return -ENXIO;
++		}
++
++		phy_id = cfg.port / 4;
++		phy_base = isp_base + IPU6_ISYS_MCD_PHY_BASE(phy_id);
++		dev_dbg(dev, "port%d PHY%u lanes %u\n", cfg.port, phy_id,
++			cfg.nlanes);
++
++		phy_config_regs = config_regs[cfg.nlanes / 2];
++		cfg.port = phy_port;
++		for (i = 0; phy_config_regs[cfg.port][i].reg; i++)
++			writel(phy_config_regs[cfg.port][i].val,
++			       phy_base + phy_config_regs[cfg.port][i].reg);
++	}
++
++	return 0;
++}
++
++#define CSI_MCD_PHY_NUM		2
++static refcount_t phy_power_ref_count[CSI_MCD_PHY_NUM];
++
++int ipu6_isys_mcd_phy_set_power(struct ipu6_isys *isys,
++				struct ipu6_isys_csi2_config *cfg,
++				const struct ipu6_isys_csi2_timing *timing,
++				bool on)
++{
++	struct device *dev = &isys->adev->auxdev.dev;
++	void __iomem *isys_base = isys->pdata->base;
++	u8 port, phy_id;
++	refcount_t *ref;
++	int ret;
++
++	port = cfg->port;
++	phy_id = port / 4;
++
++	ref = &phy_power_ref_count[phy_id];
++
++	dev_dbg(dev, "for phy %d port %d, lanes: %d\n", phy_id, port,
++		cfg->nlanes);
++
++	if (!isys_base || port >= isys->pdata->ipdata->csi2.nports) {
++		dev_warn(dev, "invalid port ID %d\n", port);
++		return -EINVAL;
++	}
++
++	if (on) {
++		if (refcount_read(ref)) {
++			dev_dbg(dev, "for phy %d is already UP", phy_id);
++			refcount_inc(ref);
++			return 0;
++		}
++
++		ret = ipu6_isys_mcd_phy_powerup_ack(isys, phy_id);
++		if (ret)
++			return ret;
++
++		ipu6_isys_mcd_phy_reset(isys, phy_id, 0);
++		ipu6_isys_mcd_phy_common_init(isys);
++
++		ret = ipu6_isys_mcd_phy_config(isys);
++		if (ret)
++			return ret;
++
++		ipu6_isys_mcd_phy_reset(isys, phy_id, 1);
++		ret = ipu6_isys_mcd_phy_ready(isys, phy_id);
++		if (ret)
++			return ret;
++
++		refcount_set(ref, 1);
++		return 0;
++	}
++
++	if (!refcount_dec_and_test(ref))
++		return 0;
++
++	return ipu6_isys_mcd_phy_powerdown_ack(isys, phy_id);
++}
+-- 
+2.43.2
+
+
+From 5d8544bd1d6dc7fce10a3bf01d10d926b8990b54 Mon Sep 17 00:00:00 2001
+From: Bingbu Cao <bingbu.cao@intel.com>
+Date: Thu, 11 Jan 2024 14:55:24 +0800
+Subject: [PATCH 17/33] media: intel/ipu6: add input system driver
+
+Input system driver do basic isys hardware setup and irq handling
+and work with fwnode and v4l2 to register the ISYS v4l2 devices.
+
+Signed-off-by: Bingbu Cao <bingbu.cao@intel.com>
+Signed-off-by: Hans de Goede <hdegoede@redhat.com>
+---
+ drivers/media/pci/intel/ipu6/ipu6-isys.c | 1353 ++++++++++++++++++++++
+ drivers/media/pci/intel/ipu6/ipu6-isys.h |  207 ++++
+ 2 files changed, 1560 insertions(+)
+ create mode 100644 drivers/media/pci/intel/ipu6/ipu6-isys.c
+ create mode 100644 drivers/media/pci/intel/ipu6/ipu6-isys.h
+
+diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys.c b/drivers/media/pci/intel/ipu6/ipu6-isys.c
+new file mode 100644
+index 000000000000..e8983363a0da
+--- /dev/null
++++ b/drivers/media/pci/intel/ipu6/ipu6-isys.c
+@@ -0,0 +1,1353 @@
++// SPDX-License-Identifier: GPL-2.0-only
++/*
++ * Copyright (C) 2013 - 2023 Intel Corporation
++ */
++
++#include <linux/auxiliary_bus.h>
++#include <linux/bitfield.h>
++#include <linux/bits.h>
++#include <linux/completion.h>
++#include <linux/container_of.h>
++#include <linux/delay.h>
++#include <linux/device.h>
++#include <linux/dma-mapping.h>
++#include <linux/err.h>
++#include <linux/firmware.h>
++#include <linux/io.h>
++#include <linux/irqreturn.h>
++#include <linux/list.h>
++#include <linux/module.h>
++#include <linux/mutex.h>
++#include <linux/pci.h>
++#include <linux/pm_runtime.h>
++#include <linux/pm_qos.h>
++#include <linux/slab.h>
++#include <linux/spinlock.h>
++#include <linux/string.h>
++
++#include <media/ipu-bridge.h>
++#include <media/media-device.h>
++#include <media/media-entity.h>
++#include <media/v4l2-async.h>
++#include <media/v4l2-device.h>
++#include <media/v4l2-fwnode.h>
++
++#include "ipu6-bus.h"
++#include "ipu6-cpd.h"
++#include "ipu6-isys.h"
++#include "ipu6-isys-csi2.h"
++#include "ipu6-mmu.h"
++#include "ipu6-platform-buttress-regs.h"
++#include "ipu6-platform-isys-csi2-reg.h"
++#include "ipu6-platform-regs.h"
++
++#define IPU6_BUTTRESS_FABIC_CONTROL		0x68
++#define GDA_ENABLE_IWAKE_INDEX			2
++#define GDA_IWAKE_THRESHOLD_INDEX		1
++#define GDA_IRQ_CRITICAL_THRESHOLD_INDEX	0
++#define GDA_MEMOPEN_THRESHOLD_INDEX		3
++#define DEFAULT_DID_RATIO			90
++#define DEFAULT_IWAKE_THRESHOLD			0x42
++#define DEFAULT_MEM_OPEN_TIME			10
++#define ONE_THOUSAND_MICROSECOND		1000
++/* One page is 2KB, 8 x 16 x 16 = 2048B = 2KB */
++#define ISF_DMA_TOP_GDA_PROFERTY_PAGE_SIZE	0x800
++
++/* LTR & DID value are 10 bit at most */
++#define LTR_DID_VAL_MAX				1023
++#define LTR_DEFAULT_VALUE			0x70503c19
++#define FILL_TIME_DEFAULT_VALUE			0xfff0783c
++#define LTR_DID_PKGC_2R				20
++#define LTR_SCALE_DEFAULT			5
++#define LTR_SCALE_1024NS			2
++#define DID_SCALE_1US				2
++#define DID_SCALE_32US				3
++#define REG_PKGC_PMON_CFG			0xb00
++
++#define VAL_PKGC_PMON_CFG_RESET			0x38
++#define VAL_PKGC_PMON_CFG_START			0x7
++
++#define IS_PIXEL_BUFFER_PAGES			0x80
++/*
++ * when iwake mode is disabled, the critical threshold is statically set
++ * to 75% of the IS pixel buffer, criticalThreshold = (128 * 3) / 4
++ */
++#define CRITICAL_THRESHOLD_IWAKE_DISABLE	(IS_PIXEL_BUFFER_PAGES * 3 / 4)
++
++union fabric_ctrl {
++	struct {
++		u16 ltr_val   : 10;
++		u16 ltr_scale : 3;
++		u16 reserved  : 3;
++		u16 did_val   : 10;
++		u16 did_scale : 3;
++		u16 reserved2 : 1;
++		u16 keep_power_in_D0   : 1;
++		u16 keep_power_override : 1;
++	} bits;
++	u32 value;
++};
++
++enum ltr_did_type {
++	LTR_IWAKE_ON,
++	LTR_IWAKE_OFF,
++	LTR_ISYS_ON,
++	LTR_ISYS_OFF,
++	LTR_ENHANNCE_IWAKE,
++	LTR_TYPE_MAX
++};
++
++#define ISYS_PM_QOS_VALUE	300
++
++static int isys_isr_one(struct ipu6_bus_device *adev);
++
++static int
++isys_complete_ext_device_registration(struct ipu6_isys *isys,
++				      struct v4l2_subdev *sd,
++				      struct ipu6_isys_csi2_config *csi2)
++{
++	struct device *dev = &isys->adev->auxdev.dev;
++	unsigned int i;
++	int ret;
++
++	for (i = 0; i < sd->entity.num_pads; i++) {
++		if (sd->entity.pads[i].flags & MEDIA_PAD_FL_SOURCE)
++			break;
++	}
++
++	if (i == sd->entity.num_pads) {
++		dev_warn(dev, "no src pad in external entity\n");
++		ret = -ENOENT;
++		goto unregister_subdev;
++	}
++
++	ret = media_create_pad_link(&sd->entity, i,
++				    &isys->csi2[csi2->port].asd.sd.entity,
++				    0, 0);
++	if (ret) {
++		dev_warn(dev, "can't create link\n");
++		goto unregister_subdev;
++	}
++
++	isys->csi2[csi2->port].nlanes = csi2->nlanes;
++
++	return 0;
++
++unregister_subdev:
++	v4l2_device_unregister_subdev(sd);
++
++	return ret;
++}
++
++static void isys_stream_init(struct ipu6_isys *isys)
++{
++	u32 i;
++
++	for (i = 0; i < IPU6_ISYS_MAX_STREAMS; i++) {
++		mutex_init(&isys->streams[i].mutex);
++		init_completion(&isys->streams[i].stream_open_completion);
++		init_completion(&isys->streams[i].stream_close_completion);
++		init_completion(&isys->streams[i].stream_start_completion);
++		init_completion(&isys->streams[i].stream_stop_completion);
++		INIT_LIST_HEAD(&isys->streams[i].queues);
++		isys->streams[i].isys = isys;
++		isys->streams[i].stream_handle = i;
++		isys->streams[i].vc = INVALID_VC_ID;
++	}
++}
++
++static void isys_csi2_unregister_subdevices(struct ipu6_isys *isys)
++{
++	const struct ipu6_isys_internal_csi2_pdata *csi2 =
++		&isys->pdata->ipdata->csi2;
++	unsigned int i;
++
++	for (i = 0; i < csi2->nports; i++)
++		ipu6_isys_csi2_cleanup(&isys->csi2[i]);
++}
++
++static int isys_csi2_register_subdevices(struct ipu6_isys *isys)
++{
++	const struct ipu6_isys_internal_csi2_pdata *csi2_pdata =
++		&isys->pdata->ipdata->csi2;
++	struct device *dev = &isys->adev->auxdev.dev;
++	unsigned int i;
++	int ret;
++
++	isys->csi2 = devm_kcalloc(dev, csi2_pdata->nports,
++				  sizeof(*isys->csi2), GFP_KERNEL);
++	if (!isys->csi2)
++		return -ENOMEM;
++
++	for (i = 0; i < csi2_pdata->nports; i++) {
++		ret = ipu6_isys_csi2_init(&isys->csi2[i], isys,
++					  isys->pdata->base +
++					  csi2_pdata->offsets[i], i);
++		if (ret)
++			goto fail;
++
++		isys->isr_csi2_bits |= IPU6_ISYS_UNISPART_IRQ_CSI2(i);
++	}
++
++	return 0;
++
++fail:
++	while (i--)
++		ipu6_isys_csi2_cleanup(&isys->csi2[i]);
++
++	return ret;
++}
++
++static int isys_csi2_create_media_links(struct ipu6_isys *isys)
++{
++	const struct ipu6_isys_internal_csi2_pdata *csi2_pdata =
++		&isys->pdata->ipdata->csi2;
++	struct device *dev = &isys->adev->auxdev.dev;
++	unsigned int i, j, k;
++	int ret;
++
++	for (i = 0; i < csi2_pdata->nports; i++) {
++		struct media_entity *sd = &isys->csi2[i].asd.sd.entity;
++
++		for (j = 0; j < NR_OF_VIDEO_DEVICE; j++) {
++			struct media_entity *v = &isys->av[j].vdev.entity;
++			u32 flag = MEDIA_LNK_FL_DYNAMIC;
++
++			for (k = CSI2_PAD_SRC; k < NR_OF_CSI2_PADS; k++) {
++				ret = media_create_pad_link(sd, k, v, 0, flag);
++				if (ret) {
++					dev_err(dev, "CSI2 can't create link\n");
++					return ret;
++				}
++			}
++		}
++	}
++
++	return 0;
++}
++
++static void isys_unregister_video_devices(struct ipu6_isys *isys)
++{
++	unsigned int i;
++
++	for (i = 0; i < NR_OF_VIDEO_DEVICE; i++)
++		ipu6_isys_video_cleanup(&isys->av[i]);
++}
++
++static int isys_register_video_devices(struct ipu6_isys *isys)
++{
++	unsigned int i;
++	int ret;
++
++	for (i = 0; i < NR_OF_VIDEO_DEVICE; i++) {
++		snprintf(isys->av[i].vdev.name, sizeof(isys->av[i].vdev.name),
++			 IPU6_ISYS_ENTITY_PREFIX " ISYS Capture %u", i);
++		isys->av[i].isys = isys;
++		isys->av[i].aq.vbq.buf_struct_size =
++			sizeof(struct ipu6_isys_video_buffer);
++		isys->av[i].pfmt = &ipu6_isys_pfmts[0];
++
++		ret = ipu6_isys_video_init(&isys->av[i]);
++		if (ret)
++			goto fail;
++	}
++
++	return 0;
++
++fail:
++	while (i--)
++		ipu6_isys_video_cleanup(&isys->av[i]);
++
++	return ret;
++}
++
++void isys_setup_hw(struct ipu6_isys *isys)
++{
++	void __iomem *base = isys->pdata->base;
++	const u8 *thd = isys->pdata->ipdata->hw_variant.cdc_fifo_threshold;
++	u32 irqs = 0;
++	unsigned int i, nports;
++
++	nports = isys->pdata->ipdata->csi2.nports;
++
++	/* Enable irqs for all MIPI ports */
++	for (i = 0; i < nports; i++)
++		irqs |= IPU6_ISYS_UNISPART_IRQ_CSI2(i);
++
++	writel(irqs, base + isys->pdata->ipdata->csi2.ctrl0_irq_edge);
++	writel(irqs, base + isys->pdata->ipdata->csi2.ctrl0_irq_lnp);
++	writel(irqs, base + isys->pdata->ipdata->csi2.ctrl0_irq_mask);
++	writel(irqs, base + isys->pdata->ipdata->csi2.ctrl0_irq_enable);
++	writel(GENMASK(19, 0),
++	       base + isys->pdata->ipdata->csi2.ctrl0_irq_clear);
++
++	irqs = ISYS_UNISPART_IRQS;
++	writel(irqs, base + IPU6_REG_ISYS_UNISPART_IRQ_EDGE);
++	writel(irqs, base + IPU6_REG_ISYS_UNISPART_IRQ_LEVEL_NOT_PULSE);
++	writel(GENMASK(28, 0), base + IPU6_REG_ISYS_UNISPART_IRQ_CLEAR);
++	writel(irqs, base + IPU6_REG_ISYS_UNISPART_IRQ_MASK);
++	writel(irqs, base + IPU6_REG_ISYS_UNISPART_IRQ_ENABLE);
++
++	writel(0, base + IPU6_REG_ISYS_UNISPART_SW_IRQ_REG);
++	writel(0, base + IPU6_REG_ISYS_UNISPART_SW_IRQ_MUX_REG);
++
++	/* Write CDC FIFO threshold values for isys */
++	for (i = 0; i < isys->pdata->ipdata->hw_variant.cdc_fifos; i++)
++		writel(thd[i], base + IPU6_REG_ISYS_CDC_THRESHOLD(i));
++}
++
++static void ipu6_isys_csi2_isr(struct ipu6_isys_csi2 *csi2)
++{
++	struct ipu6_isys_stream *stream;
++	unsigned int i;
++	u32 status;
++	int source;
++
++	ipu6_isys_register_errors(csi2);
++
++	status = readl(csi2->base + CSI_PORT_REG_BASE_IRQ_CSI_SYNC +
++		       CSI_PORT_REG_BASE_IRQ_STATUS_OFFSET);
++
++	writel(status, csi2->base + CSI_PORT_REG_BASE_IRQ_CSI_SYNC +
++	       CSI_PORT_REG_BASE_IRQ_CLEAR_OFFSET);
++
++	source = csi2->asd.source;
++	for (i = 0; i < NR_OF_CSI2_VC; i++) {
++		if (status & IPU_CSI_RX_IRQ_FS_VC(i)) {
++			stream = ipu6_isys_query_stream_by_source(csi2->isys,
++								  source, i);
++			if (stream) {
++				ipu6_isys_csi2_sof_event_by_stream(stream);
++				ipu6_isys_put_stream(stream);
++			}
++		}
++
++		if (status & IPU_CSI_RX_IRQ_FE_VC(i)) {
++			stream = ipu6_isys_query_stream_by_source(csi2->isys,
++								  source, i);
++			if (stream) {
++				ipu6_isys_csi2_eof_event_by_stream(stream);
++				ipu6_isys_put_stream(stream);
++			}
++		}
++	}
++}
++
++irqreturn_t isys_isr(struct ipu6_bus_device *adev)
++{
++	struct ipu6_isys *isys = ipu6_bus_get_drvdata(adev);
++	void __iomem *base = isys->pdata->base;
++	u32 status_sw, status_csi;
++	u32 ctrl0_status, ctrl0_clear;
++
++	spin_lock(&isys->power_lock);
++	if (!isys->power) {
++		spin_unlock(&isys->power_lock);
++		return IRQ_NONE;
++	}
++
++	ctrl0_status = isys->pdata->ipdata->csi2.ctrl0_irq_status;
++	ctrl0_clear = isys->pdata->ipdata->csi2.ctrl0_irq_clear;
++
++	status_csi = readl(isys->pdata->base + ctrl0_status);
++	status_sw = readl(isys->pdata->base +
++			  IPU6_REG_ISYS_UNISPART_IRQ_STATUS);
++
++	writel(ISYS_UNISPART_IRQS & ~IPU6_ISYS_UNISPART_IRQ_SW,
++	       base + IPU6_REG_ISYS_UNISPART_IRQ_MASK);
++
++	do {
++		writel(status_csi, isys->pdata->base + ctrl0_clear);
++
++		writel(status_sw, isys->pdata->base +
++		       IPU6_REG_ISYS_UNISPART_IRQ_CLEAR);
++
++		if (isys->isr_csi2_bits & status_csi) {
++			unsigned int i;
++
++			for (i = 0; i < isys->pdata->ipdata->csi2.nports; i++) {
++				/* irq from not enabled port */
++				if (!isys->csi2[i].base)
++					continue;
++				if (status_csi & IPU6_ISYS_UNISPART_IRQ_CSI2(i))
++					ipu6_isys_csi2_isr(&isys->csi2[i]);
++			}
++		}
++
++		writel(0, base + IPU6_REG_ISYS_UNISPART_SW_IRQ_REG);
++
++		if (!isys_isr_one(adev))
++			status_sw = IPU6_ISYS_UNISPART_IRQ_SW;
++		else
++			status_sw = 0;
++
++		status_csi = readl(isys->pdata->base + ctrl0_status);
++		status_sw |= readl(isys->pdata->base +
++				   IPU6_REG_ISYS_UNISPART_IRQ_STATUS);
++	} while ((status_csi & isys->isr_csi2_bits) ||
++		 (status_sw & IPU6_ISYS_UNISPART_IRQ_SW));
++
++	writel(ISYS_UNISPART_IRQS, base + IPU6_REG_ISYS_UNISPART_IRQ_MASK);
++
++	spin_unlock(&isys->power_lock);
++
++	return IRQ_HANDLED;
++}
++
++static void get_lut_ltrdid(struct ipu6_isys *isys, struct ltr_did *pltr_did)
++{
++	struct isys_iwake_watermark *iwake_watermark = &isys->iwake_watermark;
++	struct ltr_did ltrdid_default;
++
++	ltrdid_default.lut_ltr.value = LTR_DEFAULT_VALUE;
++	ltrdid_default.lut_fill_time.value = FILL_TIME_DEFAULT_VALUE;
++
++	if (iwake_watermark->ltrdid.lut_ltr.value)
++		*pltr_did = iwake_watermark->ltrdid;
++	else
++		*pltr_did = ltrdid_default;
++}
++
++static int set_iwake_register(struct ipu6_isys *isys, u32 index, u32 value)
++{
++	struct device *dev = &isys->adev->auxdev.dev;
++	u32 req_id = index;
++	u32 offset = 0;
++	int ret;
++
++	ret = ipu6_fw_isys_send_proxy_token(isys, req_id, index, offset, value);
++	if (ret)
++		dev_err(dev, "write %d failed %d", index, ret);
++
++	return ret;
++}
++
++/*
++ * When input system is powered up and before enabling any new sensor capture,
++ * or after disabling any sensor capture the following values need to be set:
++ * LTR_value = LTR(usec) from calculation;
++ * LTR_scale = 2;
++ * DID_value = DID(usec) from calculation;
++ * DID_scale = 2;
++ *
++ * When input system is powered down, the LTR and DID values
++ * must be returned to the default values:
++ * LTR_value = 1023;
++ * LTR_scale = 5;
++ * DID_value = 1023;
++ * DID_scale = 2;
++ */
++static void set_iwake_ltrdid(struct ipu6_isys *isys, u16 ltr, u16 did,
++			     enum ltr_did_type use)
++{
++	struct device *dev = &isys->adev->auxdev.dev;
++	u16 ltr_val, ltr_scale = LTR_SCALE_1024NS;
++	u16 did_val, did_scale = DID_SCALE_1US;
++	struct ipu6_device *isp = isys->adev->isp;
++	union fabric_ctrl fc;
++
++	switch (use) {
++	case LTR_IWAKE_ON:
++		ltr_val = min_t(u16, ltr, (u16)LTR_DID_VAL_MAX);
++		did_val = min_t(u16, did, (u16)LTR_DID_VAL_MAX);
++		ltr_scale = (ltr == LTR_DID_VAL_MAX &&
++			     did == LTR_DID_VAL_MAX) ?
++			LTR_SCALE_DEFAULT : LTR_SCALE_1024NS;
++		break;
++	case LTR_ISYS_ON:
++	case LTR_IWAKE_OFF:
++		ltr_val = LTR_DID_PKGC_2R;
++		did_val = LTR_DID_PKGC_2R;
++		break;
++	case LTR_ISYS_OFF:
++		ltr_val   = LTR_DID_VAL_MAX;
++		did_val   = LTR_DID_VAL_MAX;
++		ltr_scale = LTR_SCALE_DEFAULT;
++		break;
++	case LTR_ENHANNCE_IWAKE:
++		if (ltr == LTR_DID_VAL_MAX && did == LTR_DID_VAL_MAX) {
++			ltr_val = LTR_DID_VAL_MAX;
++			did_val = LTR_DID_VAL_MAX;
++			ltr_scale = LTR_SCALE_DEFAULT;
++		} else if (did < ONE_THOUSAND_MICROSECOND) {
++			ltr_val = ltr;
++			did_val = did;
++		} else {
++			ltr_val = ltr;
++			/* div 90% value by 32 to account for scale change */
++			did_val = did / 32;
++			did_scale = DID_SCALE_32US;
++		}
++		break;
++	default:
++		ltr_val   = LTR_DID_VAL_MAX;
++		did_val   = LTR_DID_VAL_MAX;
++		ltr_scale = LTR_SCALE_DEFAULT;
++		break;
++	}
++
++	fc.value = readl(isp->base + IPU6_BUTTRESS_FABIC_CONTROL);
++	fc.bits.ltr_val = ltr_val;
++	fc.bits.ltr_scale = ltr_scale;
++	fc.bits.did_val = did_val;
++	fc.bits.did_scale = did_scale;
++
++	dev_dbg(dev, "ltr: value %u scale %u, did: value %u scale %u\n",
++		ltr_val, ltr_scale, did_val, did_scale);
++	writel(fc.value, isp->base + IPU6_BUTTRESS_FABIC_CONTROL);
++}
++
++/*
++ * Driver may clear register GDA_ENABLE_IWAKE before FW configures the
++ * stream for debug purpose. Otherwise driver should not access this register.
++ */
++static void enable_iwake(struct ipu6_isys *isys, bool enable)
++{
++	struct isys_iwake_watermark *iwake_watermark = &isys->iwake_watermark;
++	int ret;
++
++	mutex_lock(&iwake_watermark->mutex);
++
++	if (iwake_watermark->iwake_enabled == enable) {
++		mutex_unlock(&iwake_watermark->mutex);
++		return;
++	}
++
++	ret = set_iwake_register(isys, GDA_ENABLE_IWAKE_INDEX, enable);
++	if (!ret)
++		iwake_watermark->iwake_enabled = enable;
++
++	mutex_unlock(&iwake_watermark->mutex);
++}
++
++void update_watermark_setting(struct ipu6_isys *isys)
++{
++	struct isys_iwake_watermark *iwake_watermark = &isys->iwake_watermark;
++	u32 iwake_threshold, iwake_critical_threshold, page_num;
++	struct device *dev = &isys->adev->auxdev.dev;
++	u32 calc_fill_time_us = 0, ltr = 0, did = 0;
++	struct video_stream_watermark *p_watermark;
++	enum ltr_did_type ltr_did_type;
++	struct list_head *stream_node;
++	u64 isys_pb_datarate_mbs = 0;
++	u32 mem_open_threshold = 0;
++	struct ltr_did ltrdid;
++	u64 threshold_bytes;
++	u32 max_sram_size;
++	u32 shift;
++
++	shift = isys->pdata->ipdata->sram_gran_shift;
++	max_sram_size = isys->pdata->ipdata->max_sram_size;
++
++	mutex_lock(&iwake_watermark->mutex);
++	if (iwake_watermark->force_iwake_disable) {
++		set_iwake_ltrdid(isys, 0, 0, LTR_IWAKE_OFF);
++		set_iwake_register(isys, GDA_IRQ_CRITICAL_THRESHOLD_INDEX,
++				   CRITICAL_THRESHOLD_IWAKE_DISABLE);
++		goto unlock_exit;
++	}
++
++	if (list_empty(&iwake_watermark->video_list)) {
++		isys_pb_datarate_mbs = 0;
++	} else {
++		list_for_each(stream_node, &iwake_watermark->video_list) {
++			p_watermark = list_entry(stream_node,
++						 struct video_stream_watermark,
++						 stream_node);
++			isys_pb_datarate_mbs += p_watermark->stream_data_rate;
++		}
++	}
++	mutex_unlock(&iwake_watermark->mutex);
++
++	if (!isys_pb_datarate_mbs) {
++		enable_iwake(isys, false);
++		set_iwake_ltrdid(isys, 0, 0, LTR_IWAKE_OFF);
++		mutex_lock(&iwake_watermark->mutex);
++		set_iwake_register(isys, GDA_IRQ_CRITICAL_THRESHOLD_INDEX,
++				   CRITICAL_THRESHOLD_IWAKE_DISABLE);
++		goto unlock_exit;
++	}
++
++	enable_iwake(isys, true);
++	calc_fill_time_us = max_sram_size / isys_pb_datarate_mbs;
++
++	if (isys->pdata->ipdata->enhanced_iwake) {
++		ltr = isys->pdata->ipdata->ltr;
++		did = calc_fill_time_us * DEFAULT_DID_RATIO / 100;
++		ltr_did_type = LTR_ENHANNCE_IWAKE;
++	} else {
++		get_lut_ltrdid(isys, &ltrdid);
++
++		if (calc_fill_time_us <= ltrdid.lut_fill_time.bits.th0)
++			ltr = 0;
++		else if (calc_fill_time_us <= ltrdid.lut_fill_time.bits.th1)
++			ltr = ltrdid.lut_ltr.bits.val0;
++		else if (calc_fill_time_us <= ltrdid.lut_fill_time.bits.th2)
++			ltr = ltrdid.lut_ltr.bits.val1;
++		else if (calc_fill_time_us <= ltrdid.lut_fill_time.bits.th3)
++			ltr = ltrdid.lut_ltr.bits.val2;
++		else
++			ltr = ltrdid.lut_ltr.bits.val3;
++
++		did = calc_fill_time_us - ltr;
++		ltr_did_type = LTR_IWAKE_ON;
++	}
++
++	set_iwake_ltrdid(isys, ltr, did, ltr_did_type);
++
++	/* calculate iwake threshold with 2KB granularity pages */
++	threshold_bytes = did * isys_pb_datarate_mbs;
++	iwake_threshold = max_t(u32, 1, threshold_bytes >> shift);
++	iwake_threshold = min_t(u32, iwake_threshold, max_sram_size);
++
++	mutex_lock(&iwake_watermark->mutex);
++	if (isys->pdata->ipdata->enhanced_iwake) {
++		set_iwake_register(isys, GDA_IWAKE_THRESHOLD_INDEX,
++				   DEFAULT_IWAKE_THRESHOLD);
++		/* calculate number of pages that will be filled in 10 usec */
++		page_num = (DEFAULT_MEM_OPEN_TIME * isys_pb_datarate_mbs) /
++			ISF_DMA_TOP_GDA_PROFERTY_PAGE_SIZE;
++		page_num += ((DEFAULT_MEM_OPEN_TIME * isys_pb_datarate_mbs) %
++			     ISF_DMA_TOP_GDA_PROFERTY_PAGE_SIZE) ? 1 : 0;
++		mem_open_threshold = isys->pdata->ipdata->memopen_threshold;
++		mem_open_threshold = max_t(u32, mem_open_threshold, page_num);
++		dev_dbg(dev, "mem_open_threshold: %u\n", mem_open_threshold);
++		set_iwake_register(isys, GDA_MEMOPEN_THRESHOLD_INDEX,
++				   mem_open_threshold);
++	} else {
++		set_iwake_register(isys, GDA_IWAKE_THRESHOLD_INDEX,
++				   iwake_threshold);
++	}
++
++	iwake_critical_threshold = iwake_threshold +
++		(IS_PIXEL_BUFFER_PAGES - iwake_threshold) / 2;
++
++	dev_dbg(dev, "threshold: %u critical: %u\n", iwake_threshold,
++		iwake_critical_threshold);
++
++	set_iwake_register(isys, GDA_IRQ_CRITICAL_THRESHOLD_INDEX,
++			   iwake_critical_threshold);
++
++	writel(VAL_PKGC_PMON_CFG_RESET,
++	       isys->adev->isp->base + REG_PKGC_PMON_CFG);
++	writel(VAL_PKGC_PMON_CFG_START,
++	       isys->adev->isp->base + REG_PKGC_PMON_CFG);
++unlock_exit:
++	mutex_unlock(&iwake_watermark->mutex);
++}
++
++static void isys_iwake_watermark_init(struct ipu6_isys *isys)
++{
++	struct isys_iwake_watermark *iwake_watermark = &isys->iwake_watermark;
++
++	INIT_LIST_HEAD(&iwake_watermark->video_list);
++	mutex_init(&iwake_watermark->mutex);
++
++	iwake_watermark->ltrdid.lut_ltr.value = 0;
++	iwake_watermark->isys = isys;
++	iwake_watermark->iwake_enabled = false;
++	iwake_watermark->force_iwake_disable = false;
++}
++
++static void isys_iwake_watermark_cleanup(struct ipu6_isys *isys)
++{
++	struct isys_iwake_watermark *iwake_watermark = &isys->iwake_watermark;
++
++	mutex_lock(&iwake_watermark->mutex);
++	list_del(&iwake_watermark->video_list);
++	mutex_unlock(&iwake_watermark->mutex);
++
++	mutex_destroy(&iwake_watermark->mutex);
++}
++
++/* The .bound() notifier callback when a match is found */
++static int isys_notifier_bound(struct v4l2_async_notifier *notifier,
++			       struct v4l2_subdev *sd,
++			       struct v4l2_async_connection *asc)
++{
++	struct ipu6_isys *isys =
++		container_of(notifier, struct ipu6_isys, notifier);
++	struct sensor_async_sd *s_asd =
++		container_of(asc, struct sensor_async_sd, asc);
++	int ret;
++
++	ret = ipu_bridge_instantiate_vcm(sd->dev);
++	if (ret) {
++		dev_err(&isys->adev->auxdev.dev, "instantiate vcm failed\n");
++		return ret;
++	}
++
++	dev_dbg(&isys->adev->auxdev.dev, "bind %s nlanes is %d port is %d\n",
++		sd->name, s_asd->csi2.nlanes, s_asd->csi2.port);
++	ret = isys_complete_ext_device_registration(isys, sd, &s_asd->csi2);
++	if (ret)
++		return ret;
++
++	return v4l2_device_register_subdev_nodes(&isys->v4l2_dev);
++}
++
++static int isys_notifier_complete(struct v4l2_async_notifier *notifier)
++{
++	struct ipu6_isys *isys =
++		container_of(notifier, struct ipu6_isys, notifier);
++
++	return v4l2_device_register_subdev_nodes(&isys->v4l2_dev);
++}
++
++static const struct v4l2_async_notifier_operations isys_async_ops = {
++	.bound = isys_notifier_bound,
++	.complete = isys_notifier_complete,
++};
++
++#define ISYS_MAX_PORTS 8
++static int isys_notifier_init(struct ipu6_isys *isys)
++{
++	struct ipu6_device *isp = isys->adev->isp;
++	struct device *dev = &isp->pdev->dev;
++	unsigned int i;
++	int ret;
++
++	v4l2_async_nf_init(&isys->notifier, &isys->v4l2_dev);
++
++	for (i = 0; i < ISYS_MAX_PORTS; i++) {
++		struct v4l2_fwnode_endpoint vep = {
++			.bus_type = V4L2_MBUS_CSI2_DPHY
++		};
++		struct sensor_async_sd *s_asd;
++		struct fwnode_handle *ep;
++
++		ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev), i, 0,
++						FWNODE_GRAPH_ENDPOINT_NEXT);
++		if (!ep)
++			continue;
++
++		ret = v4l2_fwnode_endpoint_parse(ep, &vep);
++		if (ret) {
++			dev_err(dev, "fwnode endpoint parse failed: %d\n", ret);
++			goto err_parse;
++		}
++
++		s_asd = v4l2_async_nf_add_fwnode_remote(&isys->notifier, ep,
++							struct sensor_async_sd);
++		if (IS_ERR(s_asd)) {
++			ret = PTR_ERR(s_asd);
++			dev_err(dev, "add remove fwnode failed: %d\n", ret);
++			goto err_parse;
++		}
++
++		s_asd->csi2.port = vep.base.port;
++		s_asd->csi2.nlanes = vep.bus.mipi_csi2.num_data_lanes;
++
++		dev_dbg(dev, "remote endpoint port %d with %d lanes added\n",
++			s_asd->csi2.port, s_asd->csi2.nlanes);
++
++		fwnode_handle_put(ep);
++
++		continue;
++
++err_parse:
++		fwnode_handle_put(ep);
++		return ret;
++	}
++
++	isys->notifier.ops = &isys_async_ops;
++	ret = v4l2_async_nf_register(&isys->notifier);
++	if (ret) {
++		dev_err(dev, "failed to register async notifier : %d\n", ret);
++		v4l2_async_nf_cleanup(&isys->notifier);
++	}
++
++	return ret;
++}
++
++static void isys_notifier_cleanup(struct ipu6_isys *isys)
++{
++	v4l2_async_nf_unregister(&isys->notifier);
++	v4l2_async_nf_cleanup(&isys->notifier);
++}
++
++static int isys_register_devices(struct ipu6_isys *isys)
++{
++	struct device *dev = &isys->adev->auxdev.dev;
++	struct pci_dev *pdev = isys->adev->isp->pdev;
++	int ret;
++
++	isys->media_dev.dev = dev;
++	media_device_pci_init(&isys->media_dev,
++			      pdev, IPU6_MEDIA_DEV_MODEL_NAME);
++
++	strscpy(isys->v4l2_dev.name, isys->media_dev.model,
++		sizeof(isys->v4l2_dev.name));
++
++	ret = media_device_register(&isys->media_dev);
++	if (ret < 0)
++		goto out_media_device_unregister;
++
++	isys->v4l2_dev.mdev = &isys->media_dev;
++	isys->v4l2_dev.ctrl_handler = NULL;
++
++	ret = v4l2_device_register(dev->parent, &isys->v4l2_dev);
++	if (ret < 0)
++		goto out_media_device_unregister;
++
++	ret = isys_register_video_devices(isys);
++	if (ret)
++		goto out_v4l2_device_unregister;
++
++	ret = isys_csi2_register_subdevices(isys);
++	if (ret)
++		goto out_isys_unregister_video_device;
++
++	ret = isys_csi2_create_media_links(isys);
++	if (ret)
++		goto out_isys_unregister_subdevices;
++
++	ret = isys_notifier_init(isys);
++	if (ret)
++		goto out_isys_unregister_subdevices;
++
++	return 0;
++
++out_isys_unregister_subdevices:
++	isys_csi2_unregister_subdevices(isys);
++
++out_isys_unregister_video_device:
++	isys_unregister_video_devices(isys);
++
++out_v4l2_device_unregister:
++	v4l2_device_unregister(&isys->v4l2_dev);
++
++out_media_device_unregister:
++	media_device_unregister(&isys->media_dev);
++	media_device_cleanup(&isys->media_dev);
++
++	dev_err(dev, "failed to register isys devices\n");
++
++	return ret;
++}
++
++static void isys_unregister_devices(struct ipu6_isys *isys)
++{
++	isys_unregister_video_devices(isys);
++	isys_csi2_unregister_subdevices(isys);
++	v4l2_device_unregister(&isys->v4l2_dev);
++	media_device_unregister(&isys->media_dev);
++	media_device_cleanup(&isys->media_dev);
++}
++
++static int isys_runtime_pm_resume(struct device *dev)
++{
++	struct ipu6_bus_device *adev = to_ipu6_bus_device(dev);
++	struct ipu6_isys *isys = ipu6_bus_get_drvdata(adev);
++	struct ipu6_device *isp = adev->isp;
++	unsigned long flags;
++	int ret;
++
++	if (!isys)
++		return 0;
++
++	ret = ipu6_mmu_hw_init(adev->mmu);
++	if (ret)
++		return ret;
++
++	cpu_latency_qos_update_request(&isys->pm_qos, ISYS_PM_QOS_VALUE);
++
++	ret = ipu6_buttress_start_tsc_sync(isp);
++	if (ret)
++		return ret;
++
++	spin_lock_irqsave(&isys->power_lock, flags);
++	isys->power = 1;
++	spin_unlock_irqrestore(&isys->power_lock, flags);
++
++	isys_setup_hw(isys);
++
++	set_iwake_ltrdid(isys, 0, 0, LTR_ISYS_ON);
++
++	return 0;
++}
++
++static int isys_runtime_pm_suspend(struct device *dev)
++{
++	struct ipu6_bus_device *adev = to_ipu6_bus_device(dev);
++	struct ipu6_isys *isys;
++	unsigned long flags;
++
++	isys = dev_get_drvdata(dev);
++	if (!isys)
++		return 0;
++
++	spin_lock_irqsave(&isys->power_lock, flags);
++	isys->power = 0;
++	spin_unlock_irqrestore(&isys->power_lock, flags);
++
++	mutex_lock(&isys->mutex);
++	isys->need_reset = false;
++	mutex_unlock(&isys->mutex);
++
++	isys->phy_termcal_val = 0;
++	cpu_latency_qos_update_request(&isys->pm_qos, PM_QOS_DEFAULT_VALUE);
++
++	set_iwake_ltrdid(isys, 0, 0, LTR_ISYS_OFF);
++
++	ipu6_mmu_hw_cleanup(adev->mmu);
++
++	return 0;
++}
++
++static int isys_suspend(struct device *dev)
++{
++	struct ipu6_isys *isys = dev_get_drvdata(dev);
++
++	/* If stream is open, refuse to suspend */
++	if (isys->stream_opened)
++		return -EBUSY;
++
++	return 0;
++}
++
++static int isys_resume(struct device *dev)
++{
++	return 0;
++}
++
++static const struct dev_pm_ops isys_pm_ops = {
++	.runtime_suspend = isys_runtime_pm_suspend,
++	.runtime_resume = isys_runtime_pm_resume,
++	.suspend = isys_suspend,
++	.resume = isys_resume,
++};
++
++static void isys_remove(struct auxiliary_device *auxdev)
++{
++	struct ipu6_bus_device *adev = auxdev_to_adev(auxdev);
++	struct ipu6_isys *isys = dev_get_drvdata(&auxdev->dev);
++	struct ipu6_device *isp = adev->isp;
++	struct isys_fw_msgs *fwmsg, *safe;
++	unsigned int i;
++
++	list_for_each_entry_safe(fwmsg, safe, &isys->framebuflist, head)
++		dma_free_attrs(&auxdev->dev, sizeof(struct isys_fw_msgs),
++			       fwmsg, fwmsg->dma_addr, 0);
++
++	list_for_each_entry_safe(fwmsg, safe, &isys->framebuflist_fw, head)
++		dma_free_attrs(&auxdev->dev, sizeof(struct isys_fw_msgs),
++			       fwmsg, fwmsg->dma_addr, 0);
++
++	isys_unregister_devices(isys);
++	isys_notifier_cleanup(isys);
++
++	cpu_latency_qos_remove_request(&isys->pm_qos);
++
++	if (!isp->secure_mode) {
++		ipu6_cpd_free_pkg_dir(adev);
++		ipu6_buttress_unmap_fw_image(adev, &adev->fw_sgt);
++		release_firmware(adev->fw);
++	}
++
++	for (i = 0; i < IPU6_ISYS_MAX_STREAMS; i++)
++		mutex_destroy(&isys->streams[i].mutex);
++
++	isys_iwake_watermark_cleanup(isys);
++	mutex_destroy(&isys->stream_mutex);
++	mutex_destroy(&isys->mutex);
++}
++
++static int alloc_fw_msg_bufs(struct ipu6_isys *isys, int amount)
++{
++	struct device *dev = &isys->adev->auxdev.dev;
++	struct isys_fw_msgs *addr;
++	dma_addr_t dma_addr;
++	unsigned long flags;
++	unsigned int i;
++
++	for (i = 0; i < amount; i++) {
++		addr = dma_alloc_attrs(dev, sizeof(struct isys_fw_msgs),
++				       &dma_addr, GFP_KERNEL, 0);
++		if (!addr)
++			break;
++		addr->dma_addr = dma_addr;
++
++		spin_lock_irqsave(&isys->listlock, flags);
++		list_add(&addr->head, &isys->framebuflist);
++		spin_unlock_irqrestore(&isys->listlock, flags);
++	}
++
++	if (i == amount)
++		return 0;
++
++	spin_lock_irqsave(&isys->listlock, flags);
++	while (!list_empty(&isys->framebuflist)) {
++		addr = list_first_entry(&isys->framebuflist,
++					struct isys_fw_msgs, head);
++		list_del(&addr->head);
++		spin_unlock_irqrestore(&isys->listlock, flags);
++		dma_free_attrs(dev, sizeof(struct isys_fw_msgs), addr,
++			       addr->dma_addr, 0);
++		spin_lock_irqsave(&isys->listlock, flags);
++	}
++	spin_unlock_irqrestore(&isys->listlock, flags);
++
++	return -ENOMEM;
++}
++
++struct isys_fw_msgs *ipu6_get_fw_msg_buf(struct ipu6_isys_stream *stream)
++{
++	struct ipu6_isys *isys = stream->isys;
++	struct device *dev = &isys->adev->auxdev.dev;
++	struct isys_fw_msgs *msg;
++	unsigned long flags;
++	int ret;
++
++	spin_lock_irqsave(&isys->listlock, flags);
++	if (list_empty(&isys->framebuflist)) {
++		spin_unlock_irqrestore(&isys->listlock, flags);
++		dev_dbg(dev, "Frame list empty\n");
++
++		ret = alloc_fw_msg_bufs(isys, 5);
++		if (ret < 0)
++			return NULL;
++
++		spin_lock_irqsave(&isys->listlock, flags);
++		if (list_empty(&isys->framebuflist)) {
++			spin_unlock_irqrestore(&isys->listlock, flags);
++			dev_err(dev, "Frame list empty\n");
++			return NULL;
++		}
++	}
++	msg = list_last_entry(&isys->framebuflist, struct isys_fw_msgs, head);
++	list_move(&msg->head, &isys->framebuflist_fw);
++	spin_unlock_irqrestore(&isys->listlock, flags);
++	memset(&msg->fw_msg, 0, sizeof(msg->fw_msg));
++
++	return msg;
++}
++
++void ipu6_cleanup_fw_msg_bufs(struct ipu6_isys *isys)
++{
++	struct isys_fw_msgs *fwmsg, *fwmsg0;
++	unsigned long flags;
++
++	spin_lock_irqsave(&isys->listlock, flags);
++	list_for_each_entry_safe(fwmsg, fwmsg0, &isys->framebuflist_fw, head)
++		list_move(&fwmsg->head, &isys->framebuflist);
++	spin_unlock_irqrestore(&isys->listlock, flags);
++}
++
++void ipu6_put_fw_msg_buf(struct ipu6_isys *isys, u64 data)
++{
++	struct isys_fw_msgs *msg;
++	unsigned long flags;
++	u64 *ptr = (u64 *)data;
++
++	if (!ptr)
++		return;
++
++	spin_lock_irqsave(&isys->listlock, flags);
++	msg = container_of(ptr, struct isys_fw_msgs, fw_msg.dummy);
++	list_move(&msg->head, &isys->framebuflist);
++	spin_unlock_irqrestore(&isys->listlock, flags);
++}
++
++static int isys_probe(struct auxiliary_device *auxdev,
++		      const struct auxiliary_device_id *auxdev_id)
++{
++	struct ipu6_bus_device *adev = auxdev_to_adev(auxdev);
++	struct ipu6_device *isp = adev->isp;
++	const struct firmware *fw;
++	struct ipu6_isys *isys;
++	unsigned int i;
++	int ret;
++
++	if (!isp->bus_ready_to_probe)
++		return -EPROBE_DEFER;
++
++	isys = devm_kzalloc(&auxdev->dev, sizeof(*isys), GFP_KERNEL);
++	if (!isys)
++		return -ENOMEM;
++
++	ret = ipu6_mmu_hw_init(adev->mmu);
++	if (ret)
++		return ret;
++
++	adev->auxdrv_data =
++		(const struct ipu6_auxdrv_data *)auxdev_id->driver_data;
++	adev->auxdrv = to_auxiliary_drv(auxdev->dev.driver);
++	isys->adev = adev;
++	isys->pdata = adev->pdata;
++
++	/* initial sensor type */
++	isys->sensor_type = isys->pdata->ipdata->sensor_type_start;
++
++	spin_lock_init(&isys->streams_lock);
++	spin_lock_init(&isys->power_lock);
++	isys->power = 0;
++	isys->phy_termcal_val = 0;
++
++	mutex_init(&isys->mutex);
++	mutex_init(&isys->stream_mutex);
++
++	spin_lock_init(&isys->listlock);
++	INIT_LIST_HEAD(&isys->framebuflist);
++	INIT_LIST_HEAD(&isys->framebuflist_fw);
++
++	isys->line_align = IPU6_ISYS_2600_MEM_LINE_ALIGN;
++	isys->icache_prefetch = 0;
++
++	dev_set_drvdata(&auxdev->dev, isys);
++
++	isys_stream_init(isys);
++
++	if (!isp->secure_mode) {
++		fw = isp->cpd_fw;
++		ret = ipu6_buttress_map_fw_image(adev, fw, &adev->fw_sgt);
++		if (ret)
++			goto release_firmware;
++
++		ret = ipu6_cpd_create_pkg_dir(adev, isp->cpd_fw->data);
++		if (ret)
++			goto remove_shared_buffer;
++	}
++
++	cpu_latency_qos_add_request(&isys->pm_qos, PM_QOS_DEFAULT_VALUE);
++
++	ret = alloc_fw_msg_bufs(isys, 20);
++	if (ret < 0)
++		goto out_remove_pkg_dir_shared_buffer;
++
++	isys_iwake_watermark_init(isys);
++
++	if (is_ipu6se(adev->isp->hw_ver))
++		isys->phy_set_power = ipu6_isys_jsl_phy_set_power;
++	else if (is_ipu6ep_mtl(adev->isp->hw_ver))
++		isys->phy_set_power = ipu6_isys_dwc_phy_set_power;
++	else
++		isys->phy_set_power = ipu6_isys_mcd_phy_set_power;
++
++	ret = isys_register_devices(isys);
++	if (ret)
++		goto out_remove_pkg_dir_shared_buffer;
++
++	ipu6_mmu_hw_cleanup(adev->mmu);
++
++	return 0;
++
++out_remove_pkg_dir_shared_buffer:
++	if (!isp->secure_mode)
++		ipu6_cpd_free_pkg_dir(adev);
++remove_shared_buffer:
++	if (!isp->secure_mode)
++		ipu6_buttress_unmap_fw_image(adev, &adev->fw_sgt);
++release_firmware:
++	if (!isp->secure_mode)
++		release_firmware(adev->fw);
++
++	for (i = 0; i < IPU6_ISYS_MAX_STREAMS; i++)
++		mutex_destroy(&isys->streams[i].mutex);
++
++	mutex_destroy(&isys->mutex);
++	mutex_destroy(&isys->stream_mutex);
++
++	ipu6_mmu_hw_cleanup(adev->mmu);
++
++	return ret;
++}
++
++struct fwmsg {
++	int type;
++	char *msg;
++	bool valid_ts;
++};
++
++static const struct fwmsg fw_msg[] = {
++	{IPU6_FW_ISYS_RESP_TYPE_STREAM_OPEN_DONE, "STREAM_OPEN_DONE", 0},
++	{IPU6_FW_ISYS_RESP_TYPE_STREAM_CLOSE_ACK, "STREAM_CLOSE_ACK", 0},
++	{IPU6_FW_ISYS_RESP_TYPE_STREAM_START_ACK, "STREAM_START_ACK", 0},
++	{IPU6_FW_ISYS_RESP_TYPE_STREAM_START_AND_CAPTURE_ACK,
++	 "STREAM_START_AND_CAPTURE_ACK", 0},
++	{IPU6_FW_ISYS_RESP_TYPE_STREAM_STOP_ACK, "STREAM_STOP_ACK", 0},
++	{IPU6_FW_ISYS_RESP_TYPE_STREAM_FLUSH_ACK, "STREAM_FLUSH_ACK", 0},
++	{IPU6_FW_ISYS_RESP_TYPE_PIN_DATA_READY, "PIN_DATA_READY", 1},
++	{IPU6_FW_ISYS_RESP_TYPE_STREAM_CAPTURE_ACK, "STREAM_CAPTURE_ACK", 0},
++	{IPU6_FW_ISYS_RESP_TYPE_STREAM_START_AND_CAPTURE_DONE,
++	 "STREAM_START_AND_CAPTURE_DONE", 1},
++	{IPU6_FW_ISYS_RESP_TYPE_STREAM_CAPTURE_DONE, "STREAM_CAPTURE_DONE", 1},
++	{IPU6_FW_ISYS_RESP_TYPE_FRAME_SOF, "FRAME_SOF", 1},
++	{IPU6_FW_ISYS_RESP_TYPE_FRAME_EOF, "FRAME_EOF", 1},
++	{IPU6_FW_ISYS_RESP_TYPE_STATS_DATA_READY, "STATS_READY", 1},
++	{-1, "UNKNOWN MESSAGE", 0}
++};
++
++static u32 resp_type_to_index(int type)
++{
++	unsigned int i;
++
++	for (i = 0; i < ARRAY_SIZE(fw_msg); i++)
++		if (fw_msg[i].type == type)
++			return i;
++
++	return  ARRAY_SIZE(fw_msg) - 1;
++}
++
++static int isys_isr_one(struct ipu6_bus_device *adev)
++{
++	struct ipu6_isys *isys = ipu6_bus_get_drvdata(adev);
++	struct ipu6_fw_isys_resp_info_abi *resp;
++	struct ipu6_isys_stream *stream;
++	struct ipu6_isys_csi2 *csi2 = NULL;
++	u32 index;
++	u64 ts;
++
++	if (!isys->fwcom)
++		return 1;
++
++	resp = ipu6_fw_isys_get_resp(isys->fwcom, IPU6_BASE_MSG_RECV_QUEUES);
++	if (!resp)
++		return 1;
++
++	ts = (u64)resp->timestamp[1] << 32 | resp->timestamp[0];
++
++	index = resp_type_to_index(resp->type);
++	dev_dbg(&adev->auxdev.dev,
++		"FW resp %02d %s, stream %u, ts 0x%16.16llx, pin %d\n",
++		resp->type, fw_msg[index].msg, resp->stream_handle,
++		fw_msg[index].valid_ts ? ts : 0, resp->pin_id);
++
++	if (resp->error_info.error == IPU6_FW_ISYS_ERROR_STREAM_IN_SUSPENSION)
++		/* Suspension is kind of special case: not enough buffers */
++		dev_dbg(&adev->auxdev.dev,
++			"FW error resp SUSPENSION, details %d\n",
++			resp->error_info.error_details);
++	else if (resp->error_info.error)
++		dev_dbg(&adev->auxdev.dev,
++			"FW error resp error %d, details %d\n",
++			resp->error_info.error, resp->error_info.error_details);
++
++	if (resp->stream_handle >= IPU6_ISYS_MAX_STREAMS) {
++		dev_err(&adev->auxdev.dev, "bad stream handle %u\n",
++			resp->stream_handle);
++		goto leave;
++	}
++
++	stream = ipu6_isys_query_stream_by_handle(isys, resp->stream_handle);
++	if (!stream) {
++		dev_err(&adev->auxdev.dev, "stream of stream_handle %u is unused\n",
++			resp->stream_handle);
++		goto leave;
++	}
++	stream->error = resp->error_info.error;
++
++	csi2 = ipu6_isys_subdev_to_csi2(stream->asd);
++
++	switch (resp->type) {
++	case IPU6_FW_ISYS_RESP_TYPE_STREAM_OPEN_DONE:
++		complete(&stream->stream_open_completion);
++		break;
++	case IPU6_FW_ISYS_RESP_TYPE_STREAM_CLOSE_ACK:
++		complete(&stream->stream_close_completion);
++		break;
++	case IPU6_FW_ISYS_RESP_TYPE_STREAM_START_ACK:
++		complete(&stream->stream_start_completion);
++		break;
++	case IPU6_FW_ISYS_RESP_TYPE_STREAM_START_AND_CAPTURE_ACK:
++		complete(&stream->stream_start_completion);
++		break;
++	case IPU6_FW_ISYS_RESP_TYPE_STREAM_STOP_ACK:
++		complete(&stream->stream_stop_completion);
++		break;
++	case IPU6_FW_ISYS_RESP_TYPE_STREAM_FLUSH_ACK:
++		complete(&stream->stream_stop_completion);
++		break;
++	case IPU6_FW_ISYS_RESP_TYPE_PIN_DATA_READY:
++		/*
++		 * firmware only release the capture msg until software
++		 * get pin_data_ready event
++		 */
++		ipu6_put_fw_msg_buf(ipu6_bus_get_drvdata(adev), resp->buf_id);
++		if (resp->pin_id < IPU6_ISYS_OUTPUT_PINS &&
++		    stream->output_pins[resp->pin_id].pin_ready)
++			stream->output_pins[resp->pin_id].pin_ready(stream,
++								    resp);
++		else
++			dev_warn(&adev->auxdev.dev,
++				 "%d:No data pin ready handler for pin id %d\n",
++				 resp->stream_handle, resp->pin_id);
++		if (csi2)
++			ipu6_isys_csi2_error(csi2);
++
++		break;
++	case IPU6_FW_ISYS_RESP_TYPE_STREAM_CAPTURE_ACK:
++		break;
++	case IPU6_FW_ISYS_RESP_TYPE_STREAM_START_AND_CAPTURE_DONE:
++	case IPU6_FW_ISYS_RESP_TYPE_STREAM_CAPTURE_DONE:
++		break;
++	case IPU6_FW_ISYS_RESP_TYPE_FRAME_SOF:
++
++		ipu6_isys_csi2_sof_event_by_stream(stream);
++		stream->seq[stream->seq_index].sequence =
++			atomic_read(&stream->sequence) - 1;
++		stream->seq[stream->seq_index].timestamp = ts;
++		dev_dbg(&adev->auxdev.dev,
++			"sof: handle %d: (index %u), timestamp 0x%16.16llx\n",
++			resp->stream_handle,
++			stream->seq[stream->seq_index].sequence, ts);
++		stream->seq_index = (stream->seq_index + 1)
++			% IPU6_ISYS_MAX_PARALLEL_SOF;
++		break;
++	case IPU6_FW_ISYS_RESP_TYPE_FRAME_EOF:
++		ipu6_isys_csi2_eof_event_by_stream(stream);
++		dev_dbg(&adev->auxdev.dev,
++			"eof: handle %d: (index %u), timestamp 0x%16.16llx\n",
++			resp->stream_handle,
++			stream->seq[stream->seq_index].sequence, ts);
++		break;
++	case IPU6_FW_ISYS_RESP_TYPE_STATS_DATA_READY:
++		break;
++	default:
++		dev_err(&adev->auxdev.dev, "%d:unknown response type %u\n",
++			resp->stream_handle, resp->type);
++		break;
++	}
++
++	ipu6_isys_put_stream(stream);
++leave:
++	ipu6_fw_isys_put_resp(isys->fwcom, IPU6_BASE_MSG_RECV_QUEUES);
++	return 0;
++}
++
++static const struct ipu6_auxdrv_data ipu6_isys_auxdrv_data = {
++	.isr = isys_isr,
++	.isr_threaded = NULL,
++	.wake_isr_thread = false,
++};
++
++static const struct auxiliary_device_id ipu6_isys_id_table[] = {
++	{
++		.name = "intel_ipu6.isys",
++		.driver_data = (kernel_ulong_t)&ipu6_isys_auxdrv_data,
++	},
++	{ }
++};
++MODULE_DEVICE_TABLE(auxiliary, ipu6_isys_id_table);
++
++static struct auxiliary_driver isys_driver = {
++	.name = IPU6_ISYS_NAME,
++	.probe = isys_probe,
++	.remove = isys_remove,
++	.id_table = ipu6_isys_id_table,
++	.driver = {
++		.pm = &isys_pm_ops,
++	},
++};
++
++module_auxiliary_driver(isys_driver);
++
++MODULE_AUTHOR("Sakari Ailus <sakari.ailus@linux.intel.com>");
++MODULE_AUTHOR("Tianshu Qiu <tian.shu.qiu@intel.com>");
++MODULE_AUTHOR("Bingbu Cao <bingbu.cao@intel.com>");
++MODULE_AUTHOR("Yunliang Ding <yunliang.ding@intel.com>");
++MODULE_AUTHOR("Hongju Wang <hongju.wang@intel.com>");
++MODULE_LICENSE("GPL");
++MODULE_DESCRIPTION("Intel IPU6 input system driver");
++MODULE_IMPORT_NS(INTEL_IPU6);
++MODULE_IMPORT_NS(INTEL_IPU_BRIDGE);
+diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys.h b/drivers/media/pci/intel/ipu6/ipu6-isys.h
+new file mode 100644
+index 000000000000..cf7a90bfedc9
+--- /dev/null
++++ b/drivers/media/pci/intel/ipu6/ipu6-isys.h
+@@ -0,0 +1,207 @@
++/* SPDX-License-Identifier: GPL-2.0-only */
++/* Copyright (C) 2013 - 2023 Intel Corporation */
++
++#ifndef IPU6_ISYS_H
++#define IPU6_ISYS_H
++
++#include <linux/irqreturn.h>
++#include <linux/list.h>
++#include <linux/mutex.h>
++#include <linux/pm_qos.h>
++#include <linux/spinlock_types.h>
++#include <linux/types.h>
++
++#include <media/media-device.h>
++#include <media/v4l2-async.h>
++#include <media/v4l2-device.h>
++
++#include "ipu6.h"
++#include "ipu6-fw-isys.h"
++#include "ipu6-isys-csi2.h"
++#include "ipu6-isys-video.h"
++
++struct ipu6_bus_device;
++
++#define IPU6_ISYS_ENTITY_PREFIX		"Intel IPU6"
++/* FW support max 16 streams */
++#define IPU6_ISYS_MAX_STREAMS		16
++#define ISYS_UNISPART_IRQS	(IPU6_ISYS_UNISPART_IRQ_SW |	\
++				 IPU6_ISYS_UNISPART_IRQ_CSI0 |	\
++				 IPU6_ISYS_UNISPART_IRQ_CSI1)
++
++#define IPU6_ISYS_2600_MEM_LINE_ALIGN	64
++
++/*
++ * Current message queue configuration. These must be big enough
++ * so that they never gets full. Queues are located in system memory
++ */
++#define IPU6_ISYS_SIZE_RECV_QUEUE 40
++#define IPU6_ISYS_SIZE_SEND_QUEUE 40
++#define IPU6_ISYS_SIZE_PROXY_RECV_QUEUE 5
++#define IPU6_ISYS_SIZE_PROXY_SEND_QUEUE 5
++#define IPU6_ISYS_NUM_RECV_QUEUE 1
++
++#define IPU6_ISYS_MIN_WIDTH		1U
++#define IPU6_ISYS_MIN_HEIGHT		1U
++#define IPU6_ISYS_MAX_WIDTH		4672U
++#define IPU6_ISYS_MAX_HEIGHT		3416U
++
++/* the threshold granularity is 2KB on IPU6 */
++#define IPU6_SRAM_GRANULARITY_SHIFT	11
++#define IPU6_SRAM_GRANULARITY_SIZE	2048
++/* the threshold granularity is 1KB on IPU6SE */
++#define IPU6SE_SRAM_GRANULARITY_SHIFT	10
++#define IPU6SE_SRAM_GRANULARITY_SIZE	1024
++/* IS pixel buffer is 256KB, MaxSRAMSize is 200KB on IPU6 */
++#define IPU6_MAX_SRAM_SIZE			(200 << 10)
++/* IS pixel buffer is 128KB, MaxSRAMSize is 96KB on IPU6SE */
++#define IPU6SE_MAX_SRAM_SIZE			(96 << 10)
++
++#define IPU6EP_LTR_VALUE			200
++#define IPU6EP_MIN_MEMOPEN_TH			0x4
++#define IPU6EP_MTL_LTR_VALUE			1023
++#define IPU6EP_MTL_MIN_MEMOPEN_TH		0xc
++
++struct ltr_did {
++	union {
++		u32 value;
++		struct {
++			u8 val0;
++			u8 val1;
++			u8 val2;
++			u8 val3;
++		} bits;
++	} lut_ltr;
++	union {
++		u32 value;
++		struct {
++			u8 th0;
++			u8 th1;
++			u8 th2;
++			u8 th3;
++		} bits;
++	} lut_fill_time;
++};
++
++struct isys_iwake_watermark {
++	bool iwake_enabled;
++	bool force_iwake_disable;
++	u32 iwake_threshold;
++	u64 isys_pixelbuffer_datarate;
++	struct ltr_did ltrdid;
++	struct mutex mutex; /* protect whole struct */
++	struct ipu6_isys *isys;
++	struct list_head video_list;
++};
++
++struct ipu6_isys_csi2_config {
++	u32 nlanes;
++	u32 port;
++};
++
++struct sensor_async_sd {
++	struct v4l2_async_connection asc;
++	struct ipu6_isys_csi2_config csi2;
++};
++
++/*
++ * struct ipu6_isys
++ *
++ * @media_dev: Media device
++ * @v4l2_dev: V4L2 device
++ * @adev: ISYS bus device
++ * @power: Is ISYS powered on or not?
++ * @isr_bits: Which bits does the ISR handle?
++ * @power_lock: Serialise access to power (power state in general)
++ * @csi2_rx_ctrl_cached: cached shared value between all CSI2 receivers
++ * @streams_lock: serialise access to streams
++ * @streams: streams per firmware stream ID
++ * @fwcom: fw communication layer private pointer
++ *         or optional external library private pointer
++ * @line_align: line alignment in memory
++ * @phy_termcal_val: the termination calibration value, only used for DWC PHY
++ * @need_reset: Isys requires d0i0->i3 transition
++ * @ref_count: total number of callers fw open
++ * @mutex: serialise access isys video open/release related operations
++ * @stream_mutex: serialise stream start and stop, queueing requests
++ * @pdata: platform data pointer
++ * @csi2: CSI-2 receivers
++ */
++struct ipu6_isys {
++	struct media_device media_dev;
++	struct v4l2_device v4l2_dev;
++	struct ipu6_bus_device *adev;
++
++	int power;
++	spinlock_t power_lock;
++	u32 isr_csi2_bits;
++	u32 csi2_rx_ctrl_cached;
++	spinlock_t streams_lock;
++	struct ipu6_isys_stream streams[IPU6_ISYS_MAX_STREAMS];
++	int streams_ref_count[IPU6_ISYS_MAX_STREAMS];
++	void *fwcom;
++	unsigned int line_align;
++	u32 phy_termcal_val;
++	bool need_reset;
++	bool icache_prefetch;
++	bool csi2_cse_ipc_not_supported;
++	unsigned int ref_count;
++	unsigned int stream_opened;
++	unsigned int sensor_type;
++
++	struct mutex mutex;
++	struct mutex stream_mutex;
++
++	struct ipu6_isys_pdata *pdata;
++
++	int (*phy_set_power)(struct ipu6_isys *isys,
++			     struct ipu6_isys_csi2_config *cfg,
++			     const struct ipu6_isys_csi2_timing *timing,
++			     bool on);
++
++	struct ipu6_isys_csi2 *csi2;
++	struct ipu6_isys_video av[NR_OF_VIDEO_DEVICE];
++
++	struct pm_qos_request pm_qos;
++	spinlock_t listlock;	/* Protect framebuflist */
++	struct list_head framebuflist;
++	struct list_head framebuflist_fw;
++	struct v4l2_async_notifier notifier;
++	struct isys_iwake_watermark iwake_watermark;
++};
++
++struct isys_fw_msgs {
++	union {
++		u64 dummy;
++		struct ipu6_fw_isys_frame_buff_set_abi frame;
++		struct ipu6_fw_isys_stream_cfg_data_abi stream;
++	} fw_msg;
++	struct list_head head;
++	dma_addr_t dma_addr;
++};
++
++struct isys_fw_msgs *ipu6_get_fw_msg_buf(struct ipu6_isys_stream *stream);
++void ipu6_put_fw_msg_buf(struct ipu6_isys *isys, u64 data);
++void ipu6_cleanup_fw_msg_bufs(struct ipu6_isys *isys);
++
++extern const struct v4l2_ioctl_ops ipu6_isys_ioctl_ops;
++
++void isys_setup_hw(struct ipu6_isys *isys);
++irqreturn_t isys_isr(struct ipu6_bus_device *adev);
++void update_watermark_setting(struct ipu6_isys *isys);
++
++int ipu6_isys_mcd_phy_set_power(struct ipu6_isys *isys,
++				struct ipu6_isys_csi2_config *cfg,
++				const struct ipu6_isys_csi2_timing *timing,
++				bool on);
++
++int ipu6_isys_dwc_phy_set_power(struct ipu6_isys *isys,
++				struct ipu6_isys_csi2_config *cfg,
++				const struct ipu6_isys_csi2_timing *timing,
++				bool on);
++
++int ipu6_isys_jsl_phy_set_power(struct ipu6_isys *isys,
++				struct ipu6_isys_csi2_config *cfg,
++				const struct ipu6_isys_csi2_timing *timing,
++				bool on);
++#endif /* IPU6_ISYS_H */
+-- 
+2.43.2
+
+
+From 7cdb944c1cc8beb6f61268e2fe177d585fa5f415 Mon Sep 17 00:00:00 2001
+From: Bingbu Cao <bingbu.cao@intel.com>
+Date: Thu, 11 Jan 2024 14:55:25 +0800
+Subject: [PATCH 18/33] media: intel/ipu6: input system video capture nodes
+
+Register v4l2 video device and setup the vb2 queue to
+support basic video capture. Video streaming callback
+will trigger the input system driver to construct a
+input system stream configuration for firmware based on
+data type and stream ID and then queue buffers to firmware
+to do capture.
+
+Signed-off-by: Bingbu Cao <bingbu.cao@intel.com>
+---
+ .../media/pci/intel/ipu6/ipu6-isys-queue.c    |  825 +++++++++++
+ .../media/pci/intel/ipu6/ipu6-isys-queue.h    |   76 +
+ .../media/pci/intel/ipu6/ipu6-isys-video.c    | 1253 +++++++++++++++++
+ .../media/pci/intel/ipu6/ipu6-isys-video.h    |  136 ++
+ 4 files changed, 2290 insertions(+)
+ create mode 100644 drivers/media/pci/intel/ipu6/ipu6-isys-queue.c
+ create mode 100644 drivers/media/pci/intel/ipu6/ipu6-isys-queue.h
+ create mode 100644 drivers/media/pci/intel/ipu6/ipu6-isys-video.c
+ create mode 100644 drivers/media/pci/intel/ipu6/ipu6-isys-video.h
+
+diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys-queue.c b/drivers/media/pci/intel/ipu6/ipu6-isys-queue.c
+new file mode 100644
+index 000000000000..735d2d642d87
+--- /dev/null
++++ b/drivers/media/pci/intel/ipu6/ipu6-isys-queue.c
+@@ -0,0 +1,825 @@
++// SPDX-License-Identifier: GPL-2.0-only
++/*
++ * Copyright (C) 2013 - 2023 Intel Corporation
++ */
++#include <linux/atomic.h>
++#include <linux/bug.h>
++#include <linux/device.h>
++#include <linux/list.h>
++#include <linux/lockdep.h>
++#include <linux/mutex.h>
++#include <linux/spinlock.h>
++#include <linux/types.h>
++
++#include <media/media-entity.h>
++#include <media/v4l2-subdev.h>
++#include <media/videobuf2-dma-contig.h>
++#include <media/videobuf2-v4l2.h>
++
++#include "ipu6-bus.h"
++#include "ipu6-fw-isys.h"
++#include "ipu6-isys.h"
++#include "ipu6-isys-video.h"
++
++static int queue_setup(struct vb2_queue *q, unsigned int *num_buffers,
++		       unsigned int *num_planes, unsigned int sizes[],
++		       struct device *alloc_devs[])
++{
++	struct ipu6_isys_queue *aq = vb2_queue_to_isys_queue(q);
++	struct ipu6_isys_video *av = ipu6_isys_queue_to_video(aq);
++	struct device *dev = &av->isys->adev->auxdev.dev;
++	bool use_fmt = false;
++	unsigned int i;
++	u32 size;
++
++	/* num_planes == 0: we're being called through VIDIOC_REQBUFS */
++	if (!*num_planes) {
++		use_fmt = true;
++		*num_planes = av->mpix.num_planes;
++	}
++
++	for (i = 0; i < *num_planes; i++) {
++		size = av->mpix.plane_fmt[i].sizeimage;
++		if (use_fmt) {
++			sizes[i] = size;
++		} else if (sizes[i] < size) {
++			dev_err(dev, "%s: queue setup: plane %d size %u < %u\n",
++				av->vdev.name, i, sizes[i], size);
++			return -EINVAL;
++		}
++
++		alloc_devs[i] = aq->dev;
++	}
++
++	return 0;
++}
++
++static int ipu6_isys_buf_prepare(struct vb2_buffer *vb)
++{
++	struct ipu6_isys_queue *aq = vb2_queue_to_isys_queue(vb->vb2_queue);
++	struct ipu6_isys_video *av = ipu6_isys_queue_to_video(aq);
++	struct device *dev = &av->isys->adev->auxdev.dev;
++
++	dev_dbg(dev, "buffer: %s: configured size %u, buffer size %lu\n",
++		av->vdev.name, av->mpix.plane_fmt[0].sizeimage,
++		vb2_plane_size(vb, 0));
++
++	if (av->mpix.plane_fmt[0].sizeimage > vb2_plane_size(vb, 0))
++		return -EINVAL;
++
++	vb2_set_plane_payload(vb, 0, av->mpix.plane_fmt[0].bytesperline *
++			      av->mpix.height);
++	vb->planes[0].data_offset = 0;
++
++	return 0;
++}
++
++/*
++ * Queue a buffer list back to incoming or active queues. The buffers
++ * are removed from the buffer list.
++ */
++void ipu6_isys_buffer_list_queue(struct ipu6_isys_buffer_list *bl,
++				 unsigned long op_flags,
++				 enum vb2_buffer_state state)
++{
++	struct ipu6_isys_buffer *ib, *ib_safe;
++	unsigned long flags;
++	bool first = true;
++
++	if (!bl)
++		return;
++
++	WARN_ON_ONCE(!bl->nbufs);
++	WARN_ON_ONCE(op_flags & IPU6_ISYS_BUFFER_LIST_FL_ACTIVE &&
++		     op_flags & IPU6_ISYS_BUFFER_LIST_FL_INCOMING);
++
++	list_for_each_entry_safe(ib, ib_safe, &bl->head, head) {
++		struct ipu6_isys_video *av;
++		struct vb2_buffer *vb = ipu6_isys_buffer_to_vb2_buffer(ib);
++		struct ipu6_isys_queue *aq =
++			vb2_queue_to_isys_queue(vb->vb2_queue);
++		struct device *dev;
++
++		av = ipu6_isys_queue_to_video(aq);
++		dev = &av->isys->adev->auxdev.dev;
++		spin_lock_irqsave(&aq->lock, flags);
++		list_del(&ib->head);
++		if (op_flags & IPU6_ISYS_BUFFER_LIST_FL_ACTIVE)
++			list_add(&ib->head, &aq->active);
++		else if (op_flags & IPU6_ISYS_BUFFER_LIST_FL_INCOMING)
++			list_add_tail(&ib->head, &aq->incoming);
++		spin_unlock_irqrestore(&aq->lock, flags);
++
++		if (op_flags & IPU6_ISYS_BUFFER_LIST_FL_SET_STATE)
++			vb2_buffer_done(vb, state);
++
++		if (first) {
++			dev_dbg(dev,
++				"queue buf list %p flags %lx, s %d, %d bufs\n",
++				bl, op_flags, state, bl->nbufs);
++			first = false;
++		}
++
++		bl->nbufs--;
++	}
++
++	WARN_ON(bl->nbufs);
++}
++
++/*
++ * flush_firmware_streamon_fail() - Flush in cases where requests may
++ * have been queued to firmware and the *firmware streamon fails for a
++ * reason or another.
++ */
++static void flush_firmware_streamon_fail(struct ipu6_isys_stream *stream)
++{
++	struct device *dev = &stream->isys->adev->auxdev.dev;
++	struct ipu6_isys_queue *aq;
++	unsigned long flags;
++
++	lockdep_assert_held(&stream->mutex);
++
++	list_for_each_entry(aq, &stream->queues, node) {
++		struct ipu6_isys_video *av = ipu6_isys_queue_to_video(aq);
++		struct ipu6_isys_buffer *ib, *ib_safe;
++
++		spin_lock_irqsave(&aq->lock, flags);
++		list_for_each_entry_safe(ib, ib_safe, &aq->active, head) {
++			struct vb2_buffer *vb =
++				ipu6_isys_buffer_to_vb2_buffer(ib);
++
++			list_del(&ib->head);
++			if (av->streaming) {
++				dev_dbg(dev,
++					"%s: queue buffer %u back to incoming\n",
++					av->vdev.name, vb->index);
++				/* Queue already streaming, return to driver. */
++				list_add(&ib->head, &aq->incoming);
++				continue;
++			}
++			/* Queue not yet streaming, return to user. */
++			dev_dbg(dev, "%s: return %u back to videobuf2\n",
++				av->vdev.name, vb->index);
++			vb2_buffer_done(ipu6_isys_buffer_to_vb2_buffer(ib),
++					VB2_BUF_STATE_QUEUED);
++		}
++		spin_unlock_irqrestore(&aq->lock, flags);
++	}
++}
++
++/*
++ * Attempt obtaining a buffer list from the incoming queues, a list of buffers
++ * that contains one entry from each video buffer queue. If a buffer can't be
++ * obtained from every queue, the buffers are returned back to the queue.
++ */
++static int buffer_list_get(struct ipu6_isys_stream *stream,
++			   struct ipu6_isys_buffer_list *bl)
++{
++	struct device *dev = &stream->isys->adev->auxdev.dev;
++	struct ipu6_isys_queue *aq;
++	unsigned long flags;
++	unsigned long buf_flag = IPU6_ISYS_BUFFER_LIST_FL_INCOMING;
++
++	bl->nbufs = 0;
++	INIT_LIST_HEAD(&bl->head);
++
++	list_for_each_entry(aq, &stream->queues, node) {
++		struct ipu6_isys_buffer *ib;
++
++		spin_lock_irqsave(&aq->lock, flags);
++		if (list_empty(&aq->incoming)) {
++			spin_unlock_irqrestore(&aq->lock, flags);
++			if (!list_empty(&bl->head))
++				ipu6_isys_buffer_list_queue(bl, buf_flag, 0);
++			return -ENODATA;
++		}
++
++		ib = list_last_entry(&aq->incoming,
++				     struct ipu6_isys_buffer, head);
++
++		dev_dbg(dev, "buffer: %s: buffer %u\n",
++			ipu6_isys_queue_to_video(aq)->vdev.name,
++			ipu6_isys_buffer_to_vb2_buffer(ib)->index);
++		list_del(&ib->head);
++		list_add(&ib->head, &bl->head);
++		spin_unlock_irqrestore(&aq->lock, flags);
++
++		bl->nbufs++;
++	}
++
++	dev_dbg(dev, "get buffer list %p, %u buffers\n", bl, bl->nbufs);
++
++	return 0;
++}
++
++static void
++ipu6_isys_buf_to_fw_frame_buf_pin(struct vb2_buffer *vb,
++				  struct ipu6_fw_isys_frame_buff_set_abi *set)
++{
++	struct ipu6_isys_queue *aq = vb2_queue_to_isys_queue(vb->vb2_queue);
++
++	set->output_pins[aq->fw_output].addr =
++		vb2_dma_contig_plane_dma_addr(vb, 0);
++	set->output_pins[aq->fw_output].out_buf_id = vb->index + 1;
++}
++
++/*
++ * Convert a buffer list to a isys fw ABI framebuffer set. The
++ * buffer list is not modified.
++ */
++#define IPU6_ISYS_FRAME_NUM_THRESHOLD  (30)
++void
++ipu6_isys_buf_to_fw_frame_buf(struct ipu6_fw_isys_frame_buff_set_abi *set,
++			      struct ipu6_isys_stream *stream,
++			      struct ipu6_isys_buffer_list *bl)
++{
++	struct ipu6_isys_buffer *ib;
++
++	WARN_ON(!bl->nbufs);
++
++	set->send_irq_sof = 1;
++	set->send_resp_sof = 1;
++	set->send_irq_eof = 0;
++	set->send_resp_eof = 0;
++
++	if (stream->streaming)
++		set->send_irq_capture_ack = 0;
++	else
++		set->send_irq_capture_ack = 1;
++	set->send_irq_capture_done = 0;
++
++	set->send_resp_capture_ack = 1;
++	set->send_resp_capture_done = 1;
++	if (atomic_read(&stream->sequence) >= IPU6_ISYS_FRAME_NUM_THRESHOLD) {
++		set->send_resp_capture_ack = 0;
++		set->send_resp_capture_done = 0;
++	}
++
++	list_for_each_entry(ib, &bl->head, head) {
++		struct vb2_buffer *vb = ipu6_isys_buffer_to_vb2_buffer(ib);
++
++		ipu6_isys_buf_to_fw_frame_buf_pin(vb, set);
++	}
++}
++
++/* Start streaming for real. The buffer list must be available. */
++static int ipu6_isys_stream_start(struct ipu6_isys_video *av,
++				  struct ipu6_isys_buffer_list *bl, bool error)
++{
++	struct ipu6_isys_stream *stream = av->stream;
++	struct device *dev = &stream->isys->adev->auxdev.dev;
++	struct ipu6_isys_buffer_list __bl;
++	int ret;
++
++	mutex_lock(&stream->isys->stream_mutex);
++	ret = ipu6_isys_video_set_streaming(av, 1, bl);
++	mutex_unlock(&stream->isys->stream_mutex);
++	if (ret)
++		goto out_requeue;
++
++	stream->streaming = 1;
++
++	bl = &__bl;
++
++	do {
++		struct ipu6_fw_isys_frame_buff_set_abi *buf = NULL;
++		struct isys_fw_msgs *msg;
++		u16 send_type = IPU6_FW_ISYS_SEND_TYPE_STREAM_CAPTURE;
++
++		ret = buffer_list_get(stream, bl);
++		if (ret < 0)
++			break;
++
++		msg = ipu6_get_fw_msg_buf(stream);
++		if (!msg)
++			return -ENOMEM;
++
++		buf = &msg->fw_msg.frame;
++		ipu6_isys_buf_to_fw_frame_buf(buf, stream, bl);
++		ipu6_fw_isys_dump_frame_buff_set(dev, buf,
++						 stream->nr_output_pins);
++		ipu6_isys_buffer_list_queue(bl, IPU6_ISYS_BUFFER_LIST_FL_ACTIVE,
++					    0);
++		ret = ipu6_fw_isys_complex_cmd(stream->isys,
++					       stream->stream_handle, buf,
++					       msg->dma_addr, sizeof(*buf),
++					       send_type);
++	} while (!WARN_ON(ret));
++
++	return 0;
++
++out_requeue:
++	if (bl && bl->nbufs)
++		ipu6_isys_buffer_list_queue(bl,
++					    (IPU6_ISYS_BUFFER_LIST_FL_INCOMING |
++					     error) ?
++					    IPU6_ISYS_BUFFER_LIST_FL_SET_STATE :
++					    0, error ? VB2_BUF_STATE_ERROR :
++					    VB2_BUF_STATE_QUEUED);
++	flush_firmware_streamon_fail(stream);
++
++	return ret;
++}
++
++static void buf_queue(struct vb2_buffer *vb)
++{
++	struct ipu6_isys_queue *aq = vb2_queue_to_isys_queue(vb->vb2_queue);
++	struct ipu6_isys_video *av = ipu6_isys_queue_to_video(aq);
++	struct vb2_v4l2_buffer *vvb = to_vb2_v4l2_buffer(vb);
++	struct ipu6_isys_video_buffer *ivb =
++		vb2_buffer_to_ipu6_isys_video_buffer(vvb);
++	struct ipu6_isys_buffer *ib = &ivb->ib;
++	struct device *dev = &av->isys->adev->auxdev.dev;
++	struct media_pipeline *media_pipe =
++		media_entity_pipeline(&av->vdev.entity);
++	struct ipu6_fw_isys_frame_buff_set_abi *buf = NULL;
++	struct ipu6_isys_stream *stream = av->stream;
++	struct ipu6_isys_buffer_list bl;
++	struct isys_fw_msgs *msg;
++	unsigned long flags;
++	dma_addr_t dma;
++	unsigned int i;
++	int ret;
++
++	dev_dbg(dev, "queue buffer %u for %s\n", vb->index, av->vdev.name);
++
++	for (i = 0; i < vb->num_planes; i++) {
++		dma = vb2_dma_contig_plane_dma_addr(vb, i);
++		dev_dbg(dev, "iova: plane %u iova %pad\n", i, &dma);
++	}
++
++	spin_lock_irqsave(&aq->lock, flags);
++	list_add(&ib->head, &aq->incoming);
++	spin_unlock_irqrestore(&aq->lock, flags);
++
++	if (!media_pipe || !vb->vb2_queue->start_streaming_called) {
++		dev_dbg(dev, "media pipeline is not ready for %s\n",
++			av->vdev.name);
++		return;
++	}
++
++	mutex_lock(&stream->mutex);
++
++	if (stream->nr_streaming != stream->nr_queues) {
++		dev_dbg(dev, "not streaming yet, adding to incoming\n");
++		goto out;
++	}
++
++	/*
++	 * We just put one buffer to the incoming list of this queue
++	 * (above). Let's see whether all queues in the pipeline would
++	 * have a buffer.
++	 */
++	ret = buffer_list_get(stream, &bl);
++	if (ret < 0) {
++		dev_warn(dev, "No buffers available\n");
++		goto out;
++	}
++
++	msg = ipu6_get_fw_msg_buf(stream);
++	if (!msg) {
++		ret = -ENOMEM;
++		goto out;
++	}
++
++	buf = &msg->fw_msg.frame;
++	ipu6_isys_buf_to_fw_frame_buf(buf, stream, &bl);
++	ipu6_fw_isys_dump_frame_buff_set(dev, buf, stream->nr_output_pins);
++
++	if (!stream->streaming) {
++		ret = ipu6_isys_stream_start(av, &bl, true);
++		if (ret)
++			dev_err(dev, "stream start failed.\n");
++		goto out;
++	}
++
++	/*
++	 * We must queue the buffers in the buffer list to the
++	 * appropriate video buffer queues BEFORE passing them to the
++	 * firmware since we could get a buffer event back before we
++	 * have queued them ourselves to the active queue.
++	 */
++	ipu6_isys_buffer_list_queue(&bl, IPU6_ISYS_BUFFER_LIST_FL_ACTIVE, 0);
++
++	ret = ipu6_fw_isys_complex_cmd(stream->isys, stream->stream_handle,
++				       buf, msg->dma_addr, sizeof(*buf),
++				       IPU6_FW_ISYS_SEND_TYPE_STREAM_CAPTURE);
++	if (ret < 0)
++		dev_err(dev, "send stream capture failed\n");
++
++out:
++	mutex_unlock(&stream->mutex);
++}
++
++static int ipu6_isys_link_fmt_validate(struct ipu6_isys_queue *aq)
++{
++	struct v4l2_mbus_framefmt format;
++	struct ipu6_isys_video *av = ipu6_isys_queue_to_video(aq);
++	struct device *dev = &av->isys->adev->auxdev.dev;
++	struct media_pad *remote_pad =
++		media_pad_remote_pad_first(av->vdev.entity.pads);
++	struct v4l2_subdev *sd;
++	u32 r_stream;
++	int ret;
++
++	if (!remote_pad)
++		return -ENOTCONN;
++
++	sd = media_entity_to_v4l2_subdev(remote_pad->entity);
++	r_stream = ipu6_isys_get_src_stream_by_src_pad(sd, remote_pad->index);
++
++	ret = ipu6_isys_get_stream_pad_fmt(sd, remote_pad->index, r_stream,
++					   &format);
++
++	if (ret) {
++		dev_dbg(dev, "failed to get %s: pad %d, stream:%d format\n",
++			sd->entity.name, remote_pad->index, r_stream);
++		return ret;
++	}
++
++	if (format.width != av->mpix.width ||
++	    format.height != av->mpix.height) {
++		dev_dbg(dev, "wrong width or height %ux%u (%ux%u expected)\n",
++			av->mpix.width, av->mpix.height,
++			format.width, format.height);
++		return -EINVAL;
++	}
++
++	if (format.field != av->mpix.field) {
++		dev_dbg(dev, "wrong field value 0x%8.8x (0x%8.8x expected)\n",
++			av->mpix.field, format.field);
++		return -EINVAL;
++	}
++
++	if (format.code != av->pfmt->code) {
++		dev_dbg(dev, "wrong mbus code 0x%8.8x (0x%8.8x expected)\n",
++			av->pfmt->code, format.code);
++		return -EINVAL;
++	}
++
++	return 0;
++}
++
++static void return_buffers(struct ipu6_isys_queue *aq,
++			   enum vb2_buffer_state state)
++{
++	struct ipu6_isys_video *av = ipu6_isys_queue_to_video(aq);
++	struct ipu6_isys_buffer *ib;
++	bool need_reset = false;
++	unsigned long flags;
++
++	spin_lock_irqsave(&aq->lock, flags);
++	while (!list_empty(&aq->incoming)) {
++		struct vb2_buffer *vb;
++
++		ib = list_first_entry(&aq->incoming, struct ipu6_isys_buffer,
++				      head);
++		vb = ipu6_isys_buffer_to_vb2_buffer(ib);
++		list_del(&ib->head);
++		spin_unlock_irqrestore(&aq->lock, flags);
++
++		vb2_buffer_done(vb, state);
++
++		spin_lock_irqsave(&aq->lock, flags);
++	}
++
++	/*
++	 * Something went wrong (FW crash / HW hang / not all buffers
++	 * returned from isys) if there are still buffers queued in active
++	 * queue. We have to clean up places a bit.
++	 */
++	while (!list_empty(&aq->active)) {
++		struct vb2_buffer *vb;
++
++		ib = list_first_entry(&aq->active, struct ipu6_isys_buffer,
++				      head);
++		vb = ipu6_isys_buffer_to_vb2_buffer(ib);
++
++		list_del(&ib->head);
++		spin_unlock_irqrestore(&aq->lock, flags);
++
++		vb2_buffer_done(vb, state);
++
++		spin_lock_irqsave(&aq->lock, flags);
++		need_reset = true;
++	}
++
++	spin_unlock_irqrestore(&aq->lock, flags);
++
++	if (need_reset) {
++		mutex_lock(&av->isys->mutex);
++		av->isys->need_reset = true;
++		mutex_unlock(&av->isys->mutex);
++	}
++}
++
++static void ipu6_isys_stream_cleanup(struct ipu6_isys_video *av)
++{
++	video_device_pipeline_stop(&av->vdev);
++	ipu6_isys_put_stream(av->stream);
++	av->stream = NULL;
++}
++
++static int start_streaming(struct vb2_queue *q, unsigned int count)
++{
++	struct ipu6_isys_queue *aq = vb2_queue_to_isys_queue(q);
++	struct ipu6_isys_video *av = ipu6_isys_queue_to_video(aq);
++	struct device *dev = &av->isys->adev->auxdev.dev;
++	struct ipu6_isys_buffer_list __bl, *bl = NULL;
++	struct ipu6_isys_stream *stream;
++	struct media_entity *source_entity = NULL;
++	int nr_queues, ret;
++
++	dev_dbg(dev, "stream: %s: width %u, height %u, css pixelformat %u\n",
++		av->vdev.name, av->mpix.width, av->mpix.height,
++		av->pfmt->css_pixelformat);
++
++	ret = ipu6_isys_setup_video(av, &source_entity, &nr_queues);
++	if (ret < 0) {
++		dev_err(dev, "failed to setup video\n");
++		goto out_return_buffers;
++	}
++
++	ret = ipu6_isys_link_fmt_validate(aq);
++	if (ret) {
++		dev_err(dev,
++			"%s: link format validation failed (%d)\n",
++			av->vdev.name, ret);
++		goto out_pipeline_stop;
++	}
++
++	ret = ipu6_isys_fw_open(av->isys);
++	if (ret)
++		goto out_pipeline_stop;
++
++	stream = av->stream;
++	mutex_lock(&stream->mutex);
++	if (!stream->nr_streaming) {
++		ret = ipu6_isys_video_prepare_stream(av, source_entity,
++						     nr_queues);
++		if (ret)
++			goto out_fw_close;
++	}
++
++	stream->nr_streaming++;
++	dev_dbg(dev, "queue %u of %u\n", stream->nr_streaming,
++		stream->nr_queues);
++
++	list_add(&aq->node, &stream->queues);
++	ipu6_isys_set_csi2_streams_status(av, true);
++	ipu6_isys_configure_stream_watermark(av, true);
++	ipu6_isys_update_stream_watermark(av, true);
++
++	if (stream->nr_streaming != stream->nr_queues)
++		goto out;
++
++	bl = &__bl;
++	ret = buffer_list_get(stream, bl);
++	if (ret < 0) {
++		dev_dbg(dev,
++			"no buffer available, postponing streamon\n");
++		goto out;
++	}
++
++	ret = ipu6_isys_stream_start(av, bl, false);
++	if (ret)
++		goto out_stream_start;
++
++out:
++	mutex_unlock(&stream->mutex);
++
++	return 0;
++
++out_stream_start:
++	list_del(&aq->node);
++	stream->nr_streaming--;
++
++out_fw_close:
++	mutex_unlock(&stream->mutex);
++	ipu6_isys_fw_close(av->isys);
++
++out_pipeline_stop:
++	ipu6_isys_stream_cleanup(av);
++
++out_return_buffers:
++	return_buffers(aq, VB2_BUF_STATE_QUEUED);
++
++	return ret;
++}
++
++static void stop_streaming(struct vb2_queue *q)
++{
++	struct ipu6_isys_queue *aq = vb2_queue_to_isys_queue(q);
++	struct ipu6_isys_video *av = ipu6_isys_queue_to_video(aq);
++	struct ipu6_isys_stream *stream = av->stream;
++
++	ipu6_isys_set_csi2_streams_status(av, false);
++
++	mutex_lock(&stream->mutex);
++
++	ipu6_isys_update_stream_watermark(av, false);
++
++	mutex_lock(&av->isys->stream_mutex);
++	if (stream->nr_streaming == stream->nr_queues && stream->streaming)
++		ipu6_isys_video_set_streaming(av, 0, NULL);
++	mutex_unlock(&av->isys->stream_mutex);
++
++	stream->nr_streaming--;
++	list_del(&aq->node);
++	stream->streaming = 0;
++	mutex_unlock(&stream->mutex);
++
++	ipu6_isys_stream_cleanup(av);
++
++	return_buffers(aq, VB2_BUF_STATE_ERROR);
++
++	ipu6_isys_fw_close(av->isys);
++}
++
++static unsigned int
++get_sof_sequence_by_timestamp(struct ipu6_isys_stream *stream,
++			      struct ipu6_fw_isys_resp_info_abi *info)
++{
++	u64 time = (u64)info->timestamp[1] << 32 | info->timestamp[0];
++	struct ipu6_isys *isys = stream->isys;
++	struct device *dev = &isys->adev->auxdev.dev;
++	unsigned int i;
++
++	/*
++	 * The timestamp is invalid as no TSC in some FPGA platform,
++	 * so get the sequence from pipeline directly in this case.
++	 */
++	if (time == 0)
++		return atomic_read(&stream->sequence) - 1;
++
++	for (i = 0; i < IPU6_ISYS_MAX_PARALLEL_SOF; i++)
++		if (time == stream->seq[i].timestamp) {
++			dev_dbg(dev, "sof: using seq nr %u for ts %llu\n",
++				stream->seq[i].sequence, time);
++			return stream->seq[i].sequence;
++		}
++
++	for (i = 0; i < IPU6_ISYS_MAX_PARALLEL_SOF; i++)
++		dev_dbg(dev, "sof: sequence %u, timestamp value %llu\n",
++			stream->seq[i].sequence, stream->seq[i].timestamp);
++
++	return 0;
++}
++
++static u64 get_sof_ns_delta(struct ipu6_isys_video *av,
++			    struct ipu6_fw_isys_resp_info_abi *info)
++{
++	struct ipu6_bus_device *adev = av->isys->adev;
++	struct ipu6_device *isp = adev->isp;
++	u64 delta, tsc_now;
++
++	ipu6_buttress_tsc_read(isp, &tsc_now);
++	if (!tsc_now)
++		return 0;
++
++	delta = tsc_now - ((u64)info->timestamp[1] << 32 | info->timestamp[0]);
++
++	return ipu6_buttress_tsc_ticks_to_ns(delta, isp);
++}
++
++void ipu6_isys_buf_calc_sequence_time(struct ipu6_isys_buffer *ib,
++				      struct ipu6_fw_isys_resp_info_abi *info)
++{
++	struct vb2_buffer *vb = ipu6_isys_buffer_to_vb2_buffer(ib);
++	struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
++	struct ipu6_isys_queue *aq = vb2_queue_to_isys_queue(vb->vb2_queue);
++	struct ipu6_isys_video *av = ipu6_isys_queue_to_video(aq);
++	struct device *dev = &av->isys->adev->auxdev.dev;
++	struct ipu6_isys_stream *stream = av->stream;
++	u64 ns;
++	u32 sequence;
++
++	ns = ktime_get_ns() - get_sof_ns_delta(av, info);
++	sequence = get_sof_sequence_by_timestamp(stream, info);
++
++	vbuf->vb2_buf.timestamp = ns;
++	vbuf->sequence = sequence;
++
++	dev_dbg(dev, "buf: %s: buffer done, CPU-timestamp:%lld, sequence:%d\n",
++		av->vdev.name, ktime_get_ns(), sequence);
++	dev_dbg(dev, "index:%d, vbuf timestamp:%lld\n", vb->index,
++		vbuf->vb2_buf.timestamp);
++}
++
++void ipu6_isys_queue_buf_done(struct ipu6_isys_buffer *ib)
++{
++	struct vb2_buffer *vb = ipu6_isys_buffer_to_vb2_buffer(ib);
++
++	if (atomic_read(&ib->str2mmio_flag)) {
++		vb2_buffer_done(vb, VB2_BUF_STATE_ERROR);
++		/*
++		 * Operation on buffer is ended with error and will be reported
++		 * to the userspace when it is de-queued
++		 */
++		atomic_set(&ib->str2mmio_flag, 0);
++	} else {
++		vb2_buffer_done(vb, VB2_BUF_STATE_DONE);
++	}
++}
++
++void ipu6_isys_queue_buf_ready(struct ipu6_isys_stream *stream,
++			       struct ipu6_fw_isys_resp_info_abi *info)
++{
++	struct ipu6_isys_queue *aq = stream->output_pins[info->pin_id].aq;
++	struct ipu6_isys *isys = stream->isys;
++	struct device *dev = &isys->adev->auxdev.dev;
++	struct ipu6_isys_buffer *ib;
++	struct vb2_buffer *vb;
++	unsigned long flags;
++	bool first = true;
++	struct vb2_v4l2_buffer *buf;
++
++	spin_lock_irqsave(&aq->lock, flags);
++	if (list_empty(&aq->active)) {
++		spin_unlock_irqrestore(&aq->lock, flags);
++		dev_err(dev, "active queue empty\n");
++		return;
++	}
++
++	list_for_each_entry_reverse(ib, &aq->active, head) {
++		dma_addr_t addr;
++
++		vb = ipu6_isys_buffer_to_vb2_buffer(ib);
++		addr = vb2_dma_contig_plane_dma_addr(vb, 0);
++
++		if (info->pin.addr != addr) {
++			if (first)
++				dev_err(dev, "Unexpected buffer address %pad\n",
++					&addr);
++			first = false;
++			continue;
++		}
++
++		if (info->error_info.error ==
++		    IPU6_FW_ISYS_ERROR_HW_REPORTED_STR2MMIO) {
++			/*
++			 * Check for error message:
++			 * 'IPU6_FW_ISYS_ERROR_HW_REPORTED_STR2MMIO'
++			 */
++			atomic_set(&ib->str2mmio_flag, 1);
++		}
++		dev_dbg(dev, "buffer: found buffer %pad\n", &addr);
++
++		buf = to_vb2_v4l2_buffer(vb);
++		buf->field = V4L2_FIELD_NONE;
++
++		list_del(&ib->head);
++		spin_unlock_irqrestore(&aq->lock, flags);
++
++		ipu6_isys_buf_calc_sequence_time(ib, info);
++
++		ipu6_isys_queue_buf_done(ib);
++
++		return;
++	}
++
++	dev_err(dev, "Failed to find a matching video buffer");
++
++	spin_unlock_irqrestore(&aq->lock, flags);
++}
++
++static const struct vb2_ops ipu6_isys_queue_ops = {
++	.queue_setup = queue_setup,
++	.wait_prepare = vb2_ops_wait_prepare,
++	.wait_finish = vb2_ops_wait_finish,
++	.buf_prepare = ipu6_isys_buf_prepare,
++	.start_streaming = start_streaming,
++	.stop_streaming = stop_streaming,
++	.buf_queue = buf_queue,
++};
++
++int ipu6_isys_queue_init(struct ipu6_isys_queue *aq)
++{
++	struct ipu6_isys *isys = ipu6_isys_queue_to_video(aq)->isys;
++	struct ipu6_isys_video *av = ipu6_isys_queue_to_video(aq);
++	int ret;
++
++	/* no support for userptr */
++	if (!aq->vbq.io_modes)
++		aq->vbq.io_modes = VB2_MMAP | VB2_DMABUF;
++
++	aq->vbq.drv_priv = aq;
++	aq->vbq.ops = &ipu6_isys_queue_ops;
++	aq->vbq.lock = &av->mutex;
++	aq->vbq.mem_ops = &vb2_dma_contig_memops;
++	aq->vbq.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE;
++	aq->vbq.timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
++
++	ret = vb2_queue_init(&aq->vbq);
++	if (ret)
++		return ret;
++
++	aq->dev = &isys->adev->auxdev.dev;
++	aq->vbq.dev = &isys->adev->auxdev.dev;
++	spin_lock_init(&aq->lock);
++	INIT_LIST_HEAD(&aq->active);
++	INIT_LIST_HEAD(&aq->incoming);
++
++	return 0;
++}
++
+diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys-queue.h b/drivers/media/pci/intel/ipu6/ipu6-isys-queue.h
+new file mode 100644
+index 000000000000..9fb454577bb5
+--- /dev/null
++++ b/drivers/media/pci/intel/ipu6/ipu6-isys-queue.h
+@@ -0,0 +1,76 @@
++/* SPDX-License-Identifier: GPL-2.0-only */
++/* Copyright (C) 2013 - 2023 Intel Corporation */
++
++#ifndef IPU6_ISYS_QUEUE_H
++#define IPU6_ISYS_QUEUE_H
++
++#include <linux/container_of.h>
++#include <linux/atomic.h>
++#include <linux/device.h>
++#include <linux/list.h>
++#include <linux/spinlock_types.h>
++
++#include <media/videobuf2-v4l2.h>
++
++#include "ipu6-fw-isys.h"
++#include "ipu6-isys-video.h"
++
++struct ipu6_isys_queue {
++	struct vb2_queue vbq;
++	struct list_head node;
++	struct device *dev;
++	/*
++	 * @lock: serialise access to queued and pre_streamon_queued
++	 */
++	spinlock_t lock;
++	struct list_head active;
++	struct list_head incoming;
++	unsigned int fw_output;
++};
++
++struct ipu6_isys_buffer {
++	struct list_head head;
++	atomic_t str2mmio_flag;
++};
++
++struct ipu6_isys_video_buffer {
++	struct vb2_v4l2_buffer vb_v4l2;
++	struct ipu6_isys_buffer ib;
++};
++
++#define IPU6_ISYS_BUFFER_LIST_FL_INCOMING	BIT(0)
++#define IPU6_ISYS_BUFFER_LIST_FL_ACTIVE	BIT(1)
++#define IPU6_ISYS_BUFFER_LIST_FL_SET_STATE	BIT(2)
++
++struct ipu6_isys_buffer_list {
++	struct list_head head;
++	unsigned int nbufs;
++};
++
++#define vb2_queue_to_isys_queue(__vb2) \
++	container_of(__vb2, struct ipu6_isys_queue, vbq)
++
++#define ipu6_isys_to_isys_video_buffer(__ib) \
++	container_of(__ib, struct ipu6_isys_video_buffer, ib)
++
++#define vb2_buffer_to_ipu6_isys_video_buffer(__vvb) \
++	container_of(__vvb, struct ipu6_isys_video_buffer, vb_v4l2)
++
++#define ipu6_isys_buffer_to_vb2_buffer(__ib) \
++	(&ipu6_isys_to_isys_video_buffer(__ib)->vb_v4l2.vb2_buf)
++
++void ipu6_isys_buffer_list_queue(struct ipu6_isys_buffer_list *bl,
++				 unsigned long op_flags,
++				 enum vb2_buffer_state state);
++void
++ipu6_isys_buf_to_fw_frame_buf(struct ipu6_fw_isys_frame_buff_set_abi *set,
++			      struct ipu6_isys_stream *stream,
++			      struct ipu6_isys_buffer_list *bl);
++void
++ipu6_isys_buf_calc_sequence_time(struct ipu6_isys_buffer *ib,
++				 struct ipu6_fw_isys_resp_info_abi *info);
++void ipu6_isys_queue_buf_done(struct ipu6_isys_buffer *ib);
++void ipu6_isys_queue_buf_ready(struct ipu6_isys_stream *stream,
++			       struct ipu6_fw_isys_resp_info_abi *info);
++int ipu6_isys_queue_init(struct ipu6_isys_queue *aq);
++#endif /* IPU6_ISYS_QUEUE_H */
+diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys-video.c b/drivers/media/pci/intel/ipu6/ipu6-isys-video.c
+new file mode 100644
+index 000000000000..847eac26bcd6
+--- /dev/null
++++ b/drivers/media/pci/intel/ipu6/ipu6-isys-video.c
+@@ -0,0 +1,1253 @@
++// SPDX-License-Identifier: GPL-2.0-only
++/*
++ * Copyright (C) 2013 - 2023 Intel Corporation
++ */
++
++#include <linux/align.h>
++#include <linux/bits.h>
++#include <linux/bug.h>
++#include <linux/completion.h>
++#include <linux/container_of.h>
++#include <linux/device.h>
++#include <linux/list.h>
++#include <linux/math64.h>
++#include <linux/minmax.h>
++#include <linux/module.h>
++#include <linux/mutex.h>
++#include <linux/pm_runtime.h>
++#include <linux/spinlock.h>
++#include <linux/string.h>
++
++#include <media/media-entity.h>
++#include <media/v4l2-ctrls.h>
++#include <media/v4l2-dev.h>
++#include <media/v4l2-fh.h>
++#include <media/v4l2-ioctl.h>
++#include <media/v4l2-subdev.h>
++#include <media/videobuf2-v4l2.h>
++
++#include "ipu6.h"
++#include "ipu6-bus.h"
++#include "ipu6-cpd.h"
++#include "ipu6-fw-isys.h"
++#include "ipu6-isys.h"
++#include "ipu6-isys-csi2.h"
++#include "ipu6-isys-queue.h"
++#include "ipu6-isys-video.h"
++#include "ipu6-platform-regs.h"
++
++const struct ipu6_isys_pixelformat ipu6_isys_pfmts[] = {
++	{V4L2_PIX_FMT_SBGGR12, 16, 12, MEDIA_BUS_FMT_SBGGR12_1X12,
++	 IPU6_FW_ISYS_FRAME_FORMAT_RAW16},
++	{V4L2_PIX_FMT_SGBRG12, 16, 12, MEDIA_BUS_FMT_SGBRG12_1X12,
++	 IPU6_FW_ISYS_FRAME_FORMAT_RAW16},
++	{V4L2_PIX_FMT_SGRBG12, 16, 12, MEDIA_BUS_FMT_SGRBG12_1X12,
++	 IPU6_FW_ISYS_FRAME_FORMAT_RAW16},
++	{V4L2_PIX_FMT_SRGGB12, 16, 12, MEDIA_BUS_FMT_SRGGB12_1X12,
++	 IPU6_FW_ISYS_FRAME_FORMAT_RAW16},
++	{V4L2_PIX_FMT_SBGGR10, 16, 10, MEDIA_BUS_FMT_SBGGR10_1X10,
++	 IPU6_FW_ISYS_FRAME_FORMAT_RAW16},
++	{V4L2_PIX_FMT_SGBRG10, 16, 10, MEDIA_BUS_FMT_SGBRG10_1X10,
++	 IPU6_FW_ISYS_FRAME_FORMAT_RAW16},
++	{V4L2_PIX_FMT_SGRBG10, 16, 10, MEDIA_BUS_FMT_SGRBG10_1X10,
++	 IPU6_FW_ISYS_FRAME_FORMAT_RAW16},
++	{V4L2_PIX_FMT_SRGGB10, 16, 10, MEDIA_BUS_FMT_SRGGB10_1X10,
++	 IPU6_FW_ISYS_FRAME_FORMAT_RAW16},
++	{V4L2_PIX_FMT_SBGGR8, 8, 8, MEDIA_BUS_FMT_SBGGR8_1X8,
++	 IPU6_FW_ISYS_FRAME_FORMAT_RAW8},
++	{V4L2_PIX_FMT_SGBRG8, 8, 8, MEDIA_BUS_FMT_SGBRG8_1X8,
++	 IPU6_FW_ISYS_FRAME_FORMAT_RAW8},
++	{V4L2_PIX_FMT_SGRBG8, 8, 8, MEDIA_BUS_FMT_SGRBG8_1X8,
++	 IPU6_FW_ISYS_FRAME_FORMAT_RAW8},
++	{V4L2_PIX_FMT_SRGGB8, 8, 8, MEDIA_BUS_FMT_SRGGB8_1X8,
++	 IPU6_FW_ISYS_FRAME_FORMAT_RAW8},
++	{V4L2_PIX_FMT_SBGGR12P, 12, 12, MEDIA_BUS_FMT_SBGGR12_1X12,
++	 IPU6_FW_ISYS_FRAME_FORMAT_RAW12},
++	{V4L2_PIX_FMT_SGBRG12P, 12, 12, MEDIA_BUS_FMT_SGBRG12_1X12,
++	 IPU6_FW_ISYS_FRAME_FORMAT_RAW12},
++	{V4L2_PIX_FMT_SGRBG12P, 12, 12, MEDIA_BUS_FMT_SGRBG12_1X12,
++	 IPU6_FW_ISYS_FRAME_FORMAT_RAW12},
++	{V4L2_PIX_FMT_SRGGB12P, 12, 12, MEDIA_BUS_FMT_SRGGB12_1X12,
++	 IPU6_FW_ISYS_FRAME_FORMAT_RAW12},
++	{V4L2_PIX_FMT_SBGGR10P, 10, 10, MEDIA_BUS_FMT_SBGGR10_1X10,
++	 IPU6_FW_ISYS_FRAME_FORMAT_RAW10},
++	{V4L2_PIX_FMT_SGBRG10P, 10, 10, MEDIA_BUS_FMT_SGBRG10_1X10,
++	 IPU6_FW_ISYS_FRAME_FORMAT_RAW10},
++	{V4L2_PIX_FMT_SGRBG10P, 10, 10, MEDIA_BUS_FMT_SGRBG10_1X10,
++	 IPU6_FW_ISYS_FRAME_FORMAT_RAW10},
++	{V4L2_PIX_FMT_SRGGB10P, 10, 10, MEDIA_BUS_FMT_SRGGB10_1X10,
++	 IPU6_FW_ISYS_FRAME_FORMAT_RAW10},
++	{V4L2_PIX_FMT_UYVY, 16, 16, MEDIA_BUS_FMT_UYVY8_1X16,
++	 IPU6_FW_ISYS_FRAME_FORMAT_UYVY},
++	{V4L2_PIX_FMT_YUYV, 16, 16, MEDIA_BUS_FMT_YUYV8_1X16,
++	 IPU6_FW_ISYS_FRAME_FORMAT_YUYV},
++	{V4L2_PIX_FMT_RGB565, 16, 16, MEDIA_BUS_FMT_RGB565_1X16,
++	 IPU6_FW_ISYS_FRAME_FORMAT_RGB565},
++	{V4L2_PIX_FMT_BGR24, 24, 24, MEDIA_BUS_FMT_RGB888_1X24,
++	 IPU6_FW_ISYS_FRAME_FORMAT_RGBA888},
++};
++
++static int video_open(struct file *file)
++{
++	struct ipu6_isys_video *av = video_drvdata(file);
++	struct ipu6_isys *isys = av->isys;
++	struct ipu6_bus_device *adev = isys->adev;
++
++	mutex_lock(&isys->mutex);
++	if (isys->need_reset) {
++		mutex_unlock(&isys->mutex);
++		dev_warn(&adev->auxdev.dev, "isys power cycle required\n");
++		return -EIO;
++	}
++	mutex_unlock(&isys->mutex);
++
++	return v4l2_fh_open(file);
++}
++
++static int video_release(struct file *file)
++{
++	return vb2_fop_release(file);
++}
++
++static const struct ipu6_isys_pixelformat *
++ipu6_isys_get_pixelformat(u32 pixelformat)
++{
++	unsigned int i;
++
++	for (i = 0; i < ARRAY_SIZE(ipu6_isys_pfmts); i++) {
++		const struct ipu6_isys_pixelformat *pfmt = &ipu6_isys_pfmts[i];
++
++		if (pfmt->pixelformat == pixelformat)
++			return pfmt;
++	}
++
++	return &ipu6_isys_pfmts[0];
++}
++
++int ipu6_isys_vidioc_querycap(struct file *file, void *fh,
++			      struct v4l2_capability *cap)
++{
++	struct ipu6_isys_video *av = video_drvdata(file);
++
++	strscpy(cap->driver, IPU6_ISYS_NAME, sizeof(cap->driver));
++	strscpy(cap->card, av->isys->media_dev.model, sizeof(cap->card));
++
++	return 0;
++}
++
++int ipu6_isys_vidioc_enum_fmt(struct file *file, void *fh,
++			      struct v4l2_fmtdesc *f)
++{
++	unsigned int i, found = 0;
++
++	if (f->index >= ARRAY_SIZE(ipu6_isys_pfmts))
++		return -EINVAL;
++
++	if (!f->mbus_code) {
++		f->flags = 0;
++		f->pixelformat = ipu6_isys_pfmts[f->index].pixelformat;
++		return 0;
++	}
++
++	for (i = 0; i < ARRAY_SIZE(ipu6_isys_pfmts); i++) {
++		if (f->mbus_code != ipu6_isys_pfmts[i].code)
++			continue;
++
++		if (f->index == found) {
++			f->flags = 0;
++			f->pixelformat = ipu6_isys_pfmts[i].pixelformat;
++			return 0;
++		}
++		found++;
++	}
++
++	return -EINVAL;
++}
++
++static int ipu6_isys_vidioc_enum_framesizes(struct file *file, void *fh,
++					    struct v4l2_frmsizeenum *fsize)
++{
++	if (fsize->index > 0)
++		return -EINVAL;
++
++	fsize->type = V4L2_FRMSIZE_TYPE_CONTINUOUS;
++	fsize->stepwise.min_width = IPU6_ISYS_MIN_WIDTH;
++	fsize->stepwise.max_width = IPU6_ISYS_MAX_WIDTH;
++	fsize->stepwise.min_height = IPU6_ISYS_MIN_HEIGHT;
++	fsize->stepwise.max_height = IPU6_ISYS_MAX_HEIGHT;
++	fsize->stepwise.step_width = 2;
++	fsize->stepwise.step_height = 2;
++
++	return 0;
++}
++
++static int vidioc_g_fmt_vid_cap_mplane(struct file *file, void *fh,
++				       struct v4l2_format *fmt)
++{
++	struct ipu6_isys_video *av = video_drvdata(file);
++
++	fmt->fmt.pix_mp = av->mpix;
++
++	return 0;
++}
++
++static const struct ipu6_isys_pixelformat *
++ipu6_isys_video_try_fmt_vid_mplane(struct ipu6_isys_video *av,
++				   struct v4l2_pix_format_mplane *mpix)
++{
++	const struct ipu6_isys_pixelformat *pfmt =
++		ipu6_isys_get_pixelformat(mpix->pixelformat);
++
++	mpix->pixelformat = pfmt->pixelformat;
++	mpix->num_planes = 1;
++
++	mpix->width = clamp(mpix->width, IPU6_ISYS_MIN_WIDTH,
++			    IPU6_ISYS_MAX_WIDTH);
++	mpix->height = clamp(mpix->height, IPU6_ISYS_MIN_HEIGHT,
++			     IPU6_ISYS_MAX_HEIGHT);
++
++	if (pfmt->bpp != pfmt->bpp_packed)
++		mpix->plane_fmt[0].bytesperline =
++			mpix->width * DIV_ROUND_UP(pfmt->bpp, BITS_PER_BYTE);
++	else
++		mpix->plane_fmt[0].bytesperline =
++			DIV_ROUND_UP((unsigned int)mpix->width * pfmt->bpp,
++				     BITS_PER_BYTE);
++
++	mpix->plane_fmt[0].bytesperline = ALIGN(mpix->plane_fmt[0].bytesperline,
++						av->isys->line_align);
++
++	/*
++	 * (height + 1) * bytesperline due to a hardware issue: the DMA unit
++	 * is a power of two, and a line should be transferred as few units
++	 * as possible. The result is that up to line length more data than
++	 * the image size may be transferred to memory after the image.
++	 * Another limitation is the GDA allocation unit size. For low
++	 * resolution it gives a bigger number. Use larger one to avoid
++	 * memory corruption.
++	 */
++	mpix->plane_fmt[0].sizeimage =
++		max(mpix->plane_fmt[0].sizeimage,
++		    mpix->plane_fmt[0].bytesperline * mpix->height +
++		    max(mpix->plane_fmt[0].bytesperline,
++			av->isys->pdata->ipdata->isys_dma_overshoot));
++
++	memset(mpix->plane_fmt[0].reserved, 0,
++	       sizeof(mpix->plane_fmt[0].reserved));
++
++	mpix->field = V4L2_FIELD_NONE;
++
++	mpix->colorspace = V4L2_COLORSPACE_RAW;
++	mpix->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT;
++	mpix->quantization = V4L2_QUANTIZATION_DEFAULT;
++	mpix->xfer_func = V4L2_XFER_FUNC_DEFAULT;
++
++	return pfmt;
++}
++
++static int vidioc_s_fmt_vid_cap_mplane(struct file *file, void *fh,
++				       struct v4l2_format *f)
++{
++	struct ipu6_isys_video *av = video_drvdata(file);
++
++	if (av->aq.vbq.streaming)
++		return -EBUSY;
++
++	av->pfmt = ipu6_isys_video_try_fmt_vid_mplane(av, &f->fmt.pix_mp);
++	av->mpix = f->fmt.pix_mp;
++
++	return 0;
++}
++
++static int vidioc_try_fmt_vid_cap_mplane(struct file *file, void *fh,
++					 struct v4l2_format *f)
++{
++	struct ipu6_isys_video *av = video_drvdata(file);
++
++	ipu6_isys_video_try_fmt_vid_mplane(av, &f->fmt.pix_mp);
++
++	return 0;
++}
++
++static int link_validate(struct media_link *link)
++{
++	struct ipu6_isys_video *av =
++		container_of(link->sink, struct ipu6_isys_video, pad);
++	struct device *dev = &av->isys->adev->auxdev.dev;
++	struct v4l2_subdev_state *s_state;
++	struct v4l2_subdev *s_sd;
++	struct v4l2_mbus_framefmt *s_fmt;
++	struct media_pad *s_pad;
++	u32 s_stream;
++	int ret = -EPIPE;
++
++	if (!link->source->entity)
++		return ret;
++
++	s_sd = media_entity_to_v4l2_subdev(link->source->entity);
++	s_state = v4l2_subdev_get_unlocked_active_state(s_sd);
++	if (!s_state)
++		return ret;
++
++	dev_dbg(dev, "validating link \"%s\":%u -> \"%s\"\n",
++		link->source->entity->name, link->source->index,
++		link->sink->entity->name);
++
++	s_pad = media_pad_remote_pad_first(&av->pad);
++	s_stream = ipu6_isys_get_src_stream_by_src_pad(s_sd, s_pad->index);
++
++	v4l2_subdev_lock_state(s_state);
++
++	s_fmt = v4l2_subdev_state_get_stream_format(s_state, s_pad->index,
++						    s_stream);
++	if (!s_fmt) {
++		dev_err(dev, "failed to get source pad format\n");
++		goto unlock;
++	}
++
++	if (s_fmt->width != av->mpix.width ||
++	    s_fmt->height != av->mpix.height || s_fmt->code != av->pfmt->code) {
++		dev_err(dev, "format mismatch %dx%d,%x != %dx%d,%x\n",
++			s_fmt->width, s_fmt->height, s_fmt->code,
++			av->mpix.width, av->mpix.height, av->pfmt->code);
++		goto unlock;
++	}
++
++	v4l2_subdev_unlock_state(s_state);
++
++	return 0;
++unlock:
++	v4l2_subdev_unlock_state(s_state);
++
++	return ret;
++}
++
++static void get_stream_opened(struct ipu6_isys_video *av)
++{
++	unsigned long flags;
++
++	spin_lock_irqsave(&av->isys->streams_lock, flags);
++	av->isys->stream_opened++;
++	spin_unlock_irqrestore(&av->isys->streams_lock, flags);
++}
++
++static void put_stream_opened(struct ipu6_isys_video *av)
++{
++	unsigned long flags;
++
++	spin_lock_irqsave(&av->isys->streams_lock, flags);
++	av->isys->stream_opened--;
++	spin_unlock_irqrestore(&av->isys->streams_lock, flags);
++}
++
++static int ipu6_isys_fw_pin_cfg(struct ipu6_isys_video *av,
++				struct ipu6_fw_isys_stream_cfg_data_abi *cfg)
++{
++	struct media_pad *src_pad = media_pad_remote_pad_first(&av->pad);
++	struct v4l2_subdev *sd = media_entity_to_v4l2_subdev(src_pad->entity);
++	struct ipu6_fw_isys_input_pin_info_abi *input_pin;
++	struct ipu6_fw_isys_output_pin_info_abi *output_pin;
++	struct ipu6_isys_stream *stream = av->stream;
++	struct ipu6_isys_queue *aq = &av->aq;
++	struct v4l2_mbus_framefmt fmt;
++	struct v4l2_rect v4l2_crop;
++	struct ipu6_isys *isys = av->isys;
++	struct device *dev = &isys->adev->auxdev.dev;
++	int input_pins = cfg->nof_input_pins++;
++	int output_pins;
++	u32 src_stream;
++	int ret;
++
++	src_stream = ipu6_isys_get_src_stream_by_src_pad(sd, src_pad->index);
++	ret = ipu6_isys_get_stream_pad_fmt(sd, src_pad->index, src_stream,
++					   &fmt);
++	if (ret < 0) {
++		dev_err(dev, "can't get stream format (%d)\n", ret);
++		return ret;
++	}
++
++	ret = ipu6_isys_get_stream_pad_crop(sd, src_pad->index, src_stream,
++					    &v4l2_crop);
++	if (ret < 0) {
++		dev_err(dev, "can't get stream crop (%d)\n", ret);
++		return ret;
++	}
++
++	input_pin = &cfg->input_pins[input_pins];
++	input_pin->input_res.width = fmt.width;
++	input_pin->input_res.height = fmt.height;
++	input_pin->dt = av->dt;
++	input_pin->bits_per_pix = av->pfmt->bpp_packed;
++	input_pin->mapped_dt = 0x40; /* invalid mipi data type */
++	input_pin->mipi_decompression = 0;
++	input_pin->capture_mode = IPU6_FW_ISYS_CAPTURE_MODE_REGULAR;
++	input_pin->mipi_store_mode = av->pfmt->bpp == av->pfmt->bpp_packed ?
++		IPU6_FW_ISYS_MIPI_STORE_MODE_DISCARD_LONG_HEADER :
++		IPU6_FW_ISYS_MIPI_STORE_MODE_NORMAL;
++	input_pin->crop_first_and_last_lines = v4l2_crop.top & 1;
++
++	output_pins = cfg->nof_output_pins++;
++	aq->fw_output = output_pins;
++	stream->output_pins[output_pins].pin_ready = ipu6_isys_queue_buf_ready;
++	stream->output_pins[output_pins].aq = aq;
++
++	output_pin = &cfg->output_pins[output_pins];
++	output_pin->input_pin_id = input_pins;
++	output_pin->output_res.width = av->mpix.width;
++	output_pin->output_res.height = av->mpix.height;
++
++	output_pin->stride = av->mpix.plane_fmt[0].bytesperline;
++	if (av->pfmt->bpp != av->pfmt->bpp_packed)
++		output_pin->pt = IPU6_FW_ISYS_PIN_TYPE_RAW_SOC;
++	else
++		output_pin->pt = IPU6_FW_ISYS_PIN_TYPE_MIPI;
++	output_pin->ft = av->pfmt->css_pixelformat;
++	output_pin->send_irq = 1;
++	memset(output_pin->ts_offsets, 0, sizeof(output_pin->ts_offsets));
++	output_pin->s2m_pixel_soc_pixel_remapping =
++		S2M_PIXEL_SOC_PIXEL_REMAPPING_FLAG_NO_REMAPPING;
++	output_pin->csi_be_soc_pixel_remapping =
++		CSI_BE_SOC_PIXEL_REMAPPING_FLAG_NO_REMAPPING;
++
++	output_pin->snoopable = true;
++	output_pin->error_handling_enable = false;
++	output_pin->sensor_type = isys->sensor_type++;
++	if (isys->sensor_type > isys->pdata->ipdata->sensor_type_end)
++		isys->sensor_type = isys->pdata->ipdata->sensor_type_start;
++
++	return 0;
++}
++
++static int start_stream_firmware(struct ipu6_isys_video *av,
++				 struct ipu6_isys_buffer_list *bl)
++{
++	struct ipu6_fw_isys_stream_cfg_data_abi *stream_cfg;
++	struct ipu6_fw_isys_frame_buff_set_abi *buf = NULL;
++	struct ipu6_isys_stream *stream = av->stream;
++	struct device *dev = &av->isys->adev->auxdev.dev;
++	struct isys_fw_msgs *msg = NULL;
++	struct ipu6_isys_queue *aq;
++	int ret, retout, tout;
++	u16 send_type;
++
++	msg = ipu6_get_fw_msg_buf(stream);
++	if (!msg)
++		return -ENOMEM;
++
++	stream_cfg = &msg->fw_msg.stream;
++	stream_cfg->src = stream->stream_source;
++	stream_cfg->vc = stream->vc;
++	stream_cfg->isl_use = 0;
++	stream_cfg->sensor_type = IPU6_FW_ISYS_SENSOR_MODE_NORMAL;
++
++	list_for_each_entry(aq, &stream->queues, node) {
++		struct ipu6_isys_video *__av = ipu6_isys_queue_to_video(aq);
++
++		ret = ipu6_isys_fw_pin_cfg(__av, stream_cfg);
++		if (ret < 0) {
++			ipu6_put_fw_msg_buf(av->isys, (u64)stream_cfg);
++			return ret;
++		}
++	}
++
++	ipu6_fw_isys_dump_stream_cfg(dev, stream_cfg);
++
++	stream->nr_output_pins = stream_cfg->nof_output_pins;
++
++	reinit_completion(&stream->stream_open_completion);
++
++	ret = ipu6_fw_isys_complex_cmd(av->isys, stream->stream_handle,
++				       stream_cfg, msg->dma_addr,
++				       sizeof(*stream_cfg),
++				       IPU6_FW_ISYS_SEND_TYPE_STREAM_OPEN);
++	if (ret < 0) {
++		dev_err(dev, "can't open stream (%d)\n", ret);
++		ipu6_put_fw_msg_buf(av->isys, (u64)stream_cfg);
++		return ret;
++	}
++
++	get_stream_opened(av);
++
++	tout = wait_for_completion_timeout(&stream->stream_open_completion,
++					   IPU6_FW_CALL_TIMEOUT_JIFFIES);
++
++	ipu6_put_fw_msg_buf(av->isys, (u64)stream_cfg);
++
++	if (!tout) {
++		dev_err(dev, "stream open time out\n");
++		ret = -ETIMEDOUT;
++		goto out_put_stream_opened;
++	}
++	if (stream->error) {
++		dev_err(dev, "stream open error: %d\n", stream->error);
++		ret = -EIO;
++		goto out_put_stream_opened;
++	}
++	dev_dbg(dev, "start stream: open complete\n");
++
++	if (bl) {
++		msg = ipu6_get_fw_msg_buf(stream);
++		if (!msg) {
++			ret = -ENOMEM;
++			goto out_put_stream_opened;
++		}
++		buf = &msg->fw_msg.frame;
++		ipu6_isys_buf_to_fw_frame_buf(buf, stream, bl);
++		ipu6_isys_buffer_list_queue(bl,
++					    IPU6_ISYS_BUFFER_LIST_FL_ACTIVE, 0);
++	}
++
++	reinit_completion(&stream->stream_start_completion);
++
++	if (bl) {
++		send_type = IPU6_FW_ISYS_SEND_TYPE_STREAM_START_AND_CAPTURE;
++		ipu6_fw_isys_dump_frame_buff_set(dev, buf,
++						 stream_cfg->nof_output_pins);
++		ret = ipu6_fw_isys_complex_cmd(av->isys, stream->stream_handle,
++					       buf, msg->dma_addr,
++					       sizeof(*buf), send_type);
++	} else {
++		send_type = IPU6_FW_ISYS_SEND_TYPE_STREAM_START;
++		ret = ipu6_fw_isys_simple_cmd(av->isys, stream->stream_handle,
++					      send_type);
++	}
++
++	if (ret < 0) {
++		dev_err(dev, "can't start streaming (%d)\n", ret);
++		goto out_stream_close;
++	}
++
++	tout = wait_for_completion_timeout(&stream->stream_start_completion,
++					   IPU6_FW_CALL_TIMEOUT_JIFFIES);
++	if (!tout) {
++		dev_err(dev, "stream start time out\n");
++		ret = -ETIMEDOUT;
++		goto out_stream_close;
++	}
++	if (stream->error) {
++		dev_err(dev, "stream start error: %d\n", stream->error);
++		ret = -EIO;
++		goto out_stream_close;
++	}
++	dev_dbg(dev, "start stream: complete\n");
++
++	return 0;
++
++out_stream_close:
++	reinit_completion(&stream->stream_close_completion);
++
++	retout = ipu6_fw_isys_simple_cmd(av->isys,
++					 stream->stream_handle,
++					 IPU6_FW_ISYS_SEND_TYPE_STREAM_CLOSE);
++	if (retout < 0) {
++		dev_dbg(dev, "can't close stream (%d)\n", retout);
++		goto out_put_stream_opened;
++	}
++
++	tout = wait_for_completion_timeout(&stream->stream_close_completion,
++					   IPU6_FW_CALL_TIMEOUT_JIFFIES);
++	if (!tout)
++		dev_err(dev, "stream close time out\n");
++	else if (stream->error)
++		dev_err(dev, "stream close error: %d\n", stream->error);
++	else
++		dev_dbg(dev, "stream close complete\n");
++
++out_put_stream_opened:
++	put_stream_opened(av);
++
++	return ret;
++}
++
++static void stop_streaming_firmware(struct ipu6_isys_video *av)
++{
++	struct device *dev = &av->isys->adev->auxdev.dev;
++	struct ipu6_isys_stream *stream = av->stream;
++	int ret, tout;
++
++	reinit_completion(&stream->stream_stop_completion);
++
++	ret = ipu6_fw_isys_simple_cmd(av->isys, stream->stream_handle,
++				      IPU6_FW_ISYS_SEND_TYPE_STREAM_FLUSH);
++
++	if (ret < 0) {
++		dev_err(dev, "can't stop stream (%d)\n", ret);
++		return;
++	}
++
++	tout = wait_for_completion_timeout(&stream->stream_stop_completion,
++					   IPU6_FW_CALL_TIMEOUT_JIFFIES);
++	if (!tout)
++		dev_warn(dev, "stream stop time out\n");
++	else if (stream->error)
++		dev_warn(dev, "stream stop error: %d\n", stream->error);
++	else
++		dev_dbg(dev, "stop stream: complete\n");
++}
++
++static void close_streaming_firmware(struct ipu6_isys_video *av)
++{
++	struct ipu6_isys_stream *stream = av->stream;
++	struct device *dev = &av->isys->adev->auxdev.dev;
++	int ret, tout;
++
++	reinit_completion(&stream->stream_close_completion);
++
++	ret = ipu6_fw_isys_simple_cmd(av->isys, stream->stream_handle,
++				      IPU6_FW_ISYS_SEND_TYPE_STREAM_CLOSE);
++	if (ret < 0) {
++		dev_err(dev, "can't close stream (%d)\n", ret);
++		return;
++	}
++
++	tout = wait_for_completion_timeout(&stream->stream_close_completion,
++					   IPU6_FW_CALL_TIMEOUT_JIFFIES);
++	if (!tout)
++		dev_warn(dev, "stream close time out\n");
++	else if (stream->error)
++		dev_warn(dev, "stream close error: %d\n", stream->error);
++	else
++		dev_dbg(dev, "close stream: complete\n");
++
++	put_stream_opened(av);
++}
++
++int ipu6_isys_video_prepare_stream(struct ipu6_isys_video *av,
++				   struct media_entity *source_entity,
++				   int nr_queues)
++{
++	struct ipu6_isys_stream *stream = av->stream;
++	struct ipu6_isys_csi2 *csi2;
++
++	if (WARN_ON(stream->nr_streaming))
++		return -EINVAL;
++
++	stream->nr_queues = nr_queues;
++	atomic_set(&stream->sequence, 0);
++
++	stream->seq_index = 0;
++	memset(stream->seq, 0, sizeof(stream->seq));
++
++	if (WARN_ON(!list_empty(&stream->queues)))
++		return -EINVAL;
++
++	stream->stream_source = stream->asd->source;
++	csi2 = ipu6_isys_subdev_to_csi2(stream->asd);
++	csi2->receiver_errors = 0;
++	stream->source_entity = source_entity;
++
++	dev_dbg(&av->isys->adev->auxdev.dev,
++		"prepare stream: external entity %s\n",
++		stream->source_entity->name);
++
++	return 0;
++}
++
++void ipu6_isys_configure_stream_watermark(struct ipu6_isys_video *av,
++					  bool state)
++{
++	struct ipu6_isys *isys = av->isys;
++	struct ipu6_isys_csi2 *csi2 = NULL;
++	struct isys_iwake_watermark *iwake_watermark = &isys->iwake_watermark;
++	struct device *dev = &isys->adev->auxdev.dev;
++	struct v4l2_mbus_framefmt format;
++	struct v4l2_subdev *esd;
++	struct v4l2_control hb = { .id = V4L2_CID_HBLANK, .value = 0 };
++	unsigned int bpp, lanes;
++	s64 link_freq = 0;
++	u64 pixel_rate = 0;
++	int ret;
++
++	if (!state)
++		return;
++
++	esd = media_entity_to_v4l2_subdev(av->stream->source_entity);
++
++	av->watermark.width = av->mpix.width;
++	av->watermark.height = av->mpix.height;
++	av->watermark.sram_gran_shift = isys->pdata->ipdata->sram_gran_shift;
++	av->watermark.sram_gran_size = isys->pdata->ipdata->sram_gran_size;
++
++	ret = v4l2_g_ctrl(esd->ctrl_handler, &hb);
++	if (!ret && hb.value >= 0)
++		av->watermark.hblank = hb.value;
++	else
++		av->watermark.hblank = 0;
++
++	csi2 = ipu6_isys_subdev_to_csi2(av->stream->asd);
++	link_freq = ipu6_isys_csi2_get_link_freq(csi2);
++	if (link_freq > 0) {
++		lanes = csi2->nlanes;
++		ret = ipu6_isys_get_stream_pad_fmt(&csi2->asd.sd, 0,
++						   av->source_stream, &format);
++		if (!ret) {
++			bpp = ipu6_isys_mbus_code_to_bpp(format.code);
++			pixel_rate = mul_u64_u32_div(link_freq, lanes * 2, bpp);
++		}
++	}
++
++	av->watermark.pixel_rate = pixel_rate;
++
++	if (!pixel_rate) {
++		mutex_lock(&iwake_watermark->mutex);
++		iwake_watermark->force_iwake_disable = true;
++		mutex_unlock(&iwake_watermark->mutex);
++		dev_warn(dev, "unexpected pixel_rate from %s, disable iwake.\n",
++			 av->stream->source_entity->name);
++	}
++}
++
++static void calculate_stream_datarate(struct ipu6_isys_video *av)
++{
++	struct video_stream_watermark *watermark = &av->watermark;
++	u32 bpp = av->pfmt->bpp;
++	u32 pages_per_line, pb_bytes_per_line, pixels_per_line, bytes_per_line;
++	u64 line_time_ns, stream_data_rate;
++	u16 shift, size;
++
++	shift = watermark->sram_gran_shift;
++	size = watermark->sram_gran_size;
++
++	pixels_per_line = watermark->width + watermark->hblank;
++	line_time_ns =  div_u64(pixels_per_line * NSEC_PER_SEC,
++				watermark->pixel_rate);
++	bytes_per_line = watermark->width * bpp / 8;
++	pages_per_line = DIV_ROUND_UP(bytes_per_line, size);
++	pb_bytes_per_line = pages_per_line << shift;
++	stream_data_rate = div64_u64(pb_bytes_per_line * 1000, line_time_ns);
++
++	watermark->stream_data_rate = stream_data_rate;
++}
++
++void ipu6_isys_update_stream_watermark(struct ipu6_isys_video *av, bool state)
++{
++	struct isys_iwake_watermark *iwake_watermark =
++		&av->isys->iwake_watermark;
++
++	if (!av->watermark.pixel_rate)
++		return;
++
++	if (state) {
++		calculate_stream_datarate(av);
++		mutex_lock(&iwake_watermark->mutex);
++		list_add(&av->watermark.stream_node,
++			 &iwake_watermark->video_list);
++		mutex_unlock(&iwake_watermark->mutex);
++	} else {
++		av->watermark.stream_data_rate = 0;
++		mutex_lock(&iwake_watermark->mutex);
++		list_del(&av->watermark.stream_node);
++		mutex_unlock(&iwake_watermark->mutex);
++	}
++
++	update_watermark_setting(av->isys);
++}
++
++void ipu6_isys_put_stream(struct ipu6_isys_stream *stream)
++{
++	struct device *dev = &stream->isys->adev->auxdev.dev;
++	unsigned int i;
++	unsigned long flags;
++
++	if (!stream) {
++		dev_err(dev, "no available stream\n");
++		return;
++	}
++
++	spin_lock_irqsave(&stream->isys->streams_lock, flags);
++	for (i = 0; i < IPU6_ISYS_MAX_STREAMS; i++) {
++		if (&stream->isys->streams[i] == stream) {
++			if (stream->isys->streams_ref_count[i] > 0)
++				stream->isys->streams_ref_count[i]--;
++			else
++				dev_warn(dev, "invalid stream %d\n", i);
++
++			break;
++		}
++	}
++	spin_unlock_irqrestore(&stream->isys->streams_lock, flags);
++}
++
++static struct ipu6_isys_stream *
++ipu6_isys_get_stream(struct ipu6_isys_video *av, struct ipu6_isys_subdev *asd)
++{
++	struct ipu6_isys_stream *stream = NULL;
++	struct ipu6_isys *isys = av->isys;
++	unsigned long flags;
++	unsigned int i;
++	u8 vc = av->vc;
++
++	if (!isys)
++		return NULL;
++
++	spin_lock_irqsave(&isys->streams_lock, flags);
++	for (i = 0; i < IPU6_ISYS_MAX_STREAMS; i++) {
++		if (isys->streams_ref_count[i] && isys->streams[i].vc == vc &&
++		    isys->streams[i].asd == asd) {
++			isys->streams_ref_count[i]++;
++			stream = &isys->streams[i];
++			break;
++		}
++	}
++
++	if (!stream) {
++		for (i = 0; i < IPU6_ISYS_MAX_STREAMS; i++) {
++			if (!isys->streams_ref_count[i]) {
++				isys->streams_ref_count[i]++;
++				stream = &isys->streams[i];
++				stream->vc = vc;
++				stream->asd = asd;
++				break;
++			}
++		}
++	}
++	spin_unlock_irqrestore(&isys->streams_lock, flags);
++
++	return stream;
++}
++
++struct ipu6_isys_stream *
++ipu6_isys_query_stream_by_handle(struct ipu6_isys *isys, u8 stream_handle)
++{
++	unsigned long flags;
++	struct ipu6_isys_stream *stream = NULL;
++
++	if (!isys)
++		return NULL;
++
++	if (stream_handle >= IPU6_ISYS_MAX_STREAMS) {
++		dev_err(&isys->adev->auxdev.dev,
++			"stream_handle %d is invalid\n", stream_handle);
++		return NULL;
++	}
++
++	spin_lock_irqsave(&isys->streams_lock, flags);
++	if (isys->streams_ref_count[stream_handle] > 0) {
++		isys->streams_ref_count[stream_handle]++;
++		stream = &isys->streams[stream_handle];
++	}
++	spin_unlock_irqrestore(&isys->streams_lock, flags);
++
++	return stream;
++}
++
++struct ipu6_isys_stream *
++ipu6_isys_query_stream_by_source(struct ipu6_isys *isys, int source, u8 vc)
++{
++	struct ipu6_isys_stream *stream = NULL;
++	unsigned long flags;
++	unsigned int i;
++
++	if (!isys)
++		return NULL;
++
++	if (source < 0) {
++		dev_err(&stream->isys->adev->auxdev.dev,
++			"query stream with invalid port number\n");
++		return NULL;
++	}
++
++	spin_lock_irqsave(&isys->streams_lock, flags);
++	for (i = 0; i < IPU6_ISYS_MAX_STREAMS; i++) {
++		if (!isys->streams_ref_count[i])
++			continue;
++
++		if (isys->streams[i].stream_source == source &&
++		    isys->streams[i].vc == vc) {
++			stream = &isys->streams[i];
++			isys->streams_ref_count[i]++;
++			break;
++		}
++	}
++	spin_unlock_irqrestore(&isys->streams_lock, flags);
++
++	return stream;
++}
++
++static u64 get_stream_mask_by_pipeline(struct ipu6_isys_video *av)
++{
++	struct media_pipeline *pipeline =
++		media_entity_pipeline(&av->vdev.entity);
++	struct media_entity *entity;
++	unsigned int i;
++	u64 stream_mask = 0;
++
++	for (i = 0; i < NR_OF_VIDEO_DEVICE; i++) {
++		entity = &av->isys->av[i].vdev.entity;
++		if (pipeline == media_entity_pipeline(entity))
++			stream_mask |= BIT_ULL(av->isys->av[i].source_stream);
++	}
++
++	return stream_mask;
++}
++
++int ipu6_isys_video_set_streaming(struct ipu6_isys_video *av, int state,
++				  struct ipu6_isys_buffer_list *bl)
++{
++	struct v4l2_subdev_krouting *routing;
++	struct ipu6_isys_stream *stream = av->stream;
++	struct v4l2_subdev_state *subdev_state;
++	struct device *dev = &av->isys->adev->auxdev.dev;
++	struct v4l2_subdev *sd = NULL;
++	struct v4l2_subdev *ssd = NULL;
++	struct media_pad *r_pad;
++	struct media_pad *s_pad = NULL;
++	u32 sink_pad, sink_stream;
++	u64 r_stream;
++	u64 stream_mask = 0;
++	int ret = 0;
++
++	dev_dbg(dev, "set stream: %d\n", state);
++
++	if (WARN(!stream->source_entity, "No source entity for stream\n"))
++		return -ENODEV;
++
++	ssd = media_entity_to_v4l2_subdev(stream->source_entity);
++	sd = &stream->asd->sd;
++	r_pad = media_pad_remote_pad_first(&av->pad);
++	r_stream = ipu6_isys_get_src_stream_by_src_pad(sd, r_pad->index);
++
++	subdev_state = v4l2_subdev_lock_and_get_active_state(sd);
++	routing = &subdev_state->routing;
++	ret = v4l2_subdev_routing_find_opposite_end(routing, r_pad->index,
++						    r_stream, &sink_pad,
++						    &sink_stream);
++	v4l2_subdev_unlock_state(subdev_state);
++	if (ret)
++		return ret;
++
++	s_pad = media_pad_remote_pad_first(&stream->asd->pad[sink_pad]);
++
++	stream_mask = get_stream_mask_by_pipeline(av);
++	if (!state) {
++		stop_streaming_firmware(av);
++
++		/* stop external sub-device now. */
++		dev_dbg(dev, "disable streams 0x%llx of %s\n", stream_mask,
++			ssd->name);
++		ret = v4l2_subdev_disable_streams(ssd, s_pad->index,
++						  stream_mask);
++		if (ret) {
++			dev_err(dev, "disable streams of %s failed with %d\n",
++				ssd->name, ret);
++			return ret;
++		}
++
++		/* stop sub-device which connects with video */
++		dev_dbg(dev, "stream off entity %s pad:%d\n", sd->name,
++			r_pad->index);
++		ret = v4l2_subdev_call(sd, video, s_stream, state);
++		if (ret) {
++			dev_err(dev, "stream off %s failed with %d\n", sd->name,
++				ret);
++			return ret;
++		}
++		close_streaming_firmware(av);
++	} else {
++		ret = start_stream_firmware(av, bl);
++		if (ret) {
++			dev_err(dev, "start stream of firmware failed\n");
++			goto out_clear_stream_watermark;
++		}
++
++		/* start sub-device which connects with video */
++		dev_dbg(dev, "stream on %s pad %d\n", sd->name, r_pad->index);
++		ret = v4l2_subdev_call(sd, video, s_stream, state);
++		if (ret) {
++			dev_err(dev, "stream on %s failed with %d\n", sd->name,
++				ret);
++			goto out_media_entity_stop_streaming_firmware;
++		}
++
++		/* start external sub-device now. */
++		dev_dbg(dev, "enable streams 0x%llx of %s\n", stream_mask,
++			ssd->name);
++		ret = v4l2_subdev_enable_streams(ssd, s_pad->index,
++						 stream_mask);
++		if (ret) {
++			dev_err(dev,
++				"enable streams 0x%llx of %s failed with %d\n",
++				stream_mask, stream->source_entity->name, ret);
++			goto out_media_entity_stop_streaming;
++		}
++	}
++
++	av->streaming = state;
++
++	return 0;
++
++out_media_entity_stop_streaming:
++	v4l2_subdev_disable_streams(sd, r_pad->index, BIT(r_stream));
++
++out_media_entity_stop_streaming_firmware:
++	stop_streaming_firmware(av);
++
++out_clear_stream_watermark:
++	ipu6_isys_update_stream_watermark(av, 0);
++
++	return ret;
++}
++
++static const struct v4l2_ioctl_ops ioctl_ops_mplane = {
++	.vidioc_querycap = ipu6_isys_vidioc_querycap,
++	.vidioc_enum_fmt_vid_cap = ipu6_isys_vidioc_enum_fmt,
++	.vidioc_enum_framesizes = ipu6_isys_vidioc_enum_framesizes,
++	.vidioc_g_fmt_vid_cap_mplane = vidioc_g_fmt_vid_cap_mplane,
++	.vidioc_s_fmt_vid_cap_mplane = vidioc_s_fmt_vid_cap_mplane,
++	.vidioc_try_fmt_vid_cap_mplane = vidioc_try_fmt_vid_cap_mplane,
++	.vidioc_reqbufs = vb2_ioctl_reqbufs,
++	.vidioc_create_bufs = vb2_ioctl_create_bufs,
++	.vidioc_prepare_buf = vb2_ioctl_prepare_buf,
++	.vidioc_querybuf = vb2_ioctl_querybuf,
++	.vidioc_qbuf = vb2_ioctl_qbuf,
++	.vidioc_dqbuf = vb2_ioctl_dqbuf,
++	.vidioc_streamon = vb2_ioctl_streamon,
++	.vidioc_streamoff = vb2_ioctl_streamoff,
++	.vidioc_expbuf = vb2_ioctl_expbuf,
++};
++
++static const struct media_entity_operations entity_ops = {
++	.link_validate = link_validate,
++};
++
++static const struct v4l2_file_operations isys_fops = {
++	.owner = THIS_MODULE,
++	.poll = vb2_fop_poll,
++	.unlocked_ioctl = video_ioctl2,
++	.mmap = vb2_fop_mmap,
++	.open = video_open,
++	.release = video_release,
++};
++
++int ipu6_isys_fw_open(struct ipu6_isys *isys)
++{
++	struct ipu6_bus_device *adev = isys->adev;
++	const struct ipu6_isys_internal_pdata *ipdata = isys->pdata->ipdata;
++	int ret;
++
++	ret = pm_runtime_resume_and_get(&adev->auxdev.dev);
++	if (ret < 0)
++		return ret;
++
++	mutex_lock(&isys->mutex);
++
++	if (isys->ref_count++)
++		goto unlock;
++
++	ipu6_configure_spc(adev->isp, &ipdata->hw_variant,
++			   IPU6_CPD_PKG_DIR_ISYS_SERVER_IDX, isys->pdata->base,
++			   adev->pkg_dir, adev->pkg_dir_dma_addr);
++
++	/*
++	 * Buffers could have been left to wrong queue at last closure.
++	 * Move them now back to empty buffer queue.
++	 */
++	ipu6_cleanup_fw_msg_bufs(isys);
++
++	if (isys->fwcom) {
++		/*
++		 * Something went wrong in previous shutdown. As we are now
++		 * restarting isys we can safely delete old context.
++		 */
++		dev_warn(&adev->auxdev.dev, "clearing old context\n");
++		ipu6_fw_isys_cleanup(isys);
++	}
++
++	ret = ipu6_fw_isys_init(isys, ipdata->num_parallel_streams);
++	if (ret < 0)
++		goto out;
++
++unlock:
++	mutex_unlock(&isys->mutex);
++
++	return 0;
++
++out:
++	isys->ref_count--;
++	mutex_unlock(&isys->mutex);
++	pm_runtime_put(&adev->auxdev.dev);
++
++	return ret;
++}
++
++void ipu6_isys_fw_close(struct ipu6_isys *isys)
++{
++	mutex_lock(&isys->mutex);
++
++	isys->ref_count--;
++	if (!isys->ref_count) {
++		ipu6_fw_isys_close(isys);
++		if (isys->fwcom) {
++			isys->need_reset = true;
++			dev_warn(&isys->adev->auxdev.dev,
++				 "failed to close fw isys\n");
++		}
++	}
++
++	mutex_unlock(&isys->mutex);
++
++	if (isys->need_reset)
++		pm_runtime_put_sync(&isys->adev->auxdev.dev);
++	else
++		pm_runtime_put(&isys->adev->auxdev.dev);
++}
++
++int ipu6_isys_setup_video(struct ipu6_isys_video *av,
++			  struct media_entity **source_entity, int *nr_queues)
++{
++	struct device *dev = &av->isys->adev->auxdev.dev;
++	struct v4l2_mbus_frame_desc_entry entry;
++	struct v4l2_subdev_route *route = NULL;
++	struct v4l2_subdev_route *r;
++	struct v4l2_subdev_state *state;
++	struct ipu6_isys_subdev *asd;
++	struct v4l2_subdev *remote_sd;
++	struct media_pipeline *pipeline;
++	struct media_pad *source_pad, *remote_pad;
++	int ret = -EINVAL;
++
++	remote_pad = media_pad_remote_pad_first(&av->pad);
++	if (!remote_pad) {
++		dev_dbg(dev, "failed to get remote pad\n");
++		return -ENODEV;
++	}
++
++	remote_sd = media_entity_to_v4l2_subdev(remote_pad->entity);
++	asd = to_ipu6_isys_subdev(remote_sd);
++	source_pad = media_pad_remote_pad_first(&remote_pad->entity->pads[0]);
++	if (!source_pad) {
++		dev_dbg(dev, "No external source entity\n");
++		return -ENODEV;
++	}
++
++	*source_entity = source_pad->entity;
++
++	/* Find the root */
++	state = v4l2_subdev_lock_and_get_active_state(remote_sd);
++	for_each_active_route(&state->routing, r) {
++		if (r->source_pad != remote_pad->index)
++			continue;
++
++		route = r;
++		break;
++	}
++
++	if (!route) {
++		v4l2_subdev_unlock_state(state);
++		dev_dbg(dev, "Failed to find route\n");
++		return -ENODEV;
++	}
++	v4l2_subdev_unlock_state(state);
++	av->source_stream = route->sink_stream;
++
++	ret = ipu6_isys_csi2_get_remote_desc(av->source_stream,
++					     to_ipu6_isys_csi2(asd),
++					     *source_entity, &entry,
++					     nr_queues);
++	if (ret == -ENOIOCTLCMD) {
++		av->vc = 0;
++		av->dt = ipu6_isys_mbus_code_to_mipi(av->pfmt->code);
++		*nr_queues = 1;
++	} else if (!ret) {
++		dev_dbg(dev, "Framedesc: stream %u, len %u, vc %u, dt %#x\n",
++			entry.stream, entry.length, entry.bus.csi2.vc,
++			entry.bus.csi2.dt);
++
++		av->vc = entry.bus.csi2.vc;
++		av->dt = entry.bus.csi2.dt;
++	} else {
++		dev_err(dev, "failed to get remote frame desc\n");
++		return ret;
++	}
++
++	pipeline = media_entity_pipeline(&av->vdev.entity);
++	if (!pipeline)
++		ret = video_device_pipeline_alloc_start(&av->vdev);
++	else
++		ret = video_device_pipeline_start(&av->vdev, pipeline);
++	if (ret < 0) {
++		dev_dbg(dev, "media pipeline start failed\n");
++		return ret;
++	}
++
++	av->stream = ipu6_isys_get_stream(av, asd);
++	if (!av->stream) {
++		video_device_pipeline_stop(&av->vdev);
++		dev_err(dev, "no available stream for firmware\n");
++		return -EINVAL;
++	}
++
++	return 0;
++}
++
++/*
++ * Do everything that's needed to initialise things related to video
++ * buffer queue, video node, and the related media entity. The caller
++ * is expected to assign isys field and set the name of the video
++ * device.
++ */
++int ipu6_isys_video_init(struct ipu6_isys_video *av)
++{
++	struct v4l2_format format = {
++		.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE,
++		.fmt.pix_mp = {
++			.width = 1920,
++			.height = 1080,
++		},
++	};
++	int ret;
++
++	mutex_init(&av->mutex);
++	av->vdev.device_caps = V4L2_CAP_STREAMING | V4L2_CAP_IO_MC |
++			       V4L2_CAP_VIDEO_CAPTURE_MPLANE;
++	av->vdev.vfl_dir = VFL_DIR_RX;
++
++	ret = ipu6_isys_queue_init(&av->aq);
++	if (ret)
++		goto out_free_watermark;
++
++	av->pad.flags = MEDIA_PAD_FL_SINK | MEDIA_PAD_FL_MUST_CONNECT;
++	ret = media_entity_pads_init(&av->vdev.entity, 1, &av->pad);
++	if (ret)
++		goto out_vb2_queue_release;
++
++	av->vdev.entity.ops = &entity_ops;
++	av->vdev.release = video_device_release_empty;
++	av->vdev.fops = &isys_fops;
++	av->vdev.v4l2_dev = &av->isys->v4l2_dev;
++	if (!av->vdev.ioctl_ops)
++		av->vdev.ioctl_ops = &ioctl_ops_mplane;
++	av->vdev.queue = &av->aq.vbq;
++	av->vdev.lock = &av->mutex;
++
++	ipu6_isys_video_try_fmt_vid_mplane(av, &format.fmt.pix_mp);
++	av->mpix = format.fmt.pix_mp;
++
++	set_bit(V4L2_FL_USES_V4L2_FH, &av->vdev.flags);
++	video_set_drvdata(&av->vdev, av);
++
++	ret = video_register_device(&av->vdev, VFL_TYPE_VIDEO, -1);
++	if (ret)
++		goto out_media_entity_cleanup;
++
++	return ret;
++
++out_media_entity_cleanup:
++	vb2_video_unregister_device(&av->vdev);
++	media_entity_cleanup(&av->vdev.entity);
++
++out_vb2_queue_release:
++	vb2_queue_release(&av->aq.vbq);
++
++out_free_watermark:
++	mutex_destroy(&av->mutex);
++
++	return ret;
++}
++
++void ipu6_isys_video_cleanup(struct ipu6_isys_video *av)
++{
++	vb2_video_unregister_device(&av->vdev);
++	media_entity_cleanup(&av->vdev.entity);
++	mutex_destroy(&av->mutex);
++}
+diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys-video.h b/drivers/media/pci/intel/ipu6/ipu6-isys-video.h
+new file mode 100644
+index 000000000000..21cd33c7e277
+--- /dev/null
++++ b/drivers/media/pci/intel/ipu6/ipu6-isys-video.h
+@@ -0,0 +1,136 @@
++/* SPDX-License-Identifier: GPL-2.0-only */
++/* Copyright (C) 2013 - 2023 Intel Corporation */
++
++#ifndef IPU6_ISYS_VIDEO_H
++#define IPU6_ISYS_VIDEO_H
++
++#include <linux/atomic.h>
++#include <linux/completion.h>
++#include <linux/container_of.h>
++#include <linux/list.h>
++#include <linux/mutex.h>
++
++#include <media/media-entity.h>
++#include <media/v4l2-dev.h>
++
++#include "ipu6-isys-queue.h"
++
++#define IPU6_ISYS_OUTPUT_PINS 11
++#define IPU6_ISYS_MAX_PARALLEL_SOF 2
++#define NR_OF_VIDEO_DEVICE 31
++
++struct file;
++struct ipu6_isys;
++struct ipu6_isys_subdev;
++
++struct ipu6_isys_pixelformat {
++	u32 pixelformat;
++	u32 bpp;
++	u32 bpp_packed;
++	u32 code;
++	u32 css_pixelformat;
++};
++
++struct sequence_info {
++	unsigned int sequence;
++	u64 timestamp;
++};
++
++struct output_pin_data {
++	void (*pin_ready)(struct ipu6_isys_stream *stream,
++			  struct ipu6_fw_isys_resp_info_abi *info);
++	struct ipu6_isys_queue *aq;
++};
++
++/*
++ * Align with firmware stream. Each stream represents a CSI virtual channel.
++ * May map to multiple video devices
++ */
++struct ipu6_isys_stream {
++	struct mutex mutex;
++	struct media_entity *source_entity;
++	atomic_t sequence;
++	unsigned int seq_index;
++	struct sequence_info seq[IPU6_ISYS_MAX_PARALLEL_SOF];
++	int stream_source;
++	int stream_handle;
++	unsigned int nr_output_pins;
++	struct ipu6_isys_subdev *asd;
++
++	int nr_queues;	/* Number of capture queues */
++	int nr_streaming;
++	int streaming;	/* Has streaming been really started? */
++	struct list_head queues;
++	struct completion stream_open_completion;
++	struct completion stream_close_completion;
++	struct completion stream_start_completion;
++	struct completion stream_stop_completion;
++	struct ipu6_isys *isys;
++
++	struct output_pin_data output_pins[IPU6_ISYS_OUTPUT_PINS];
++	int error;
++	u8 vc;
++};
++
++struct video_stream_watermark {
++	u32 width;
++	u32 height;
++	u32 hblank;
++	u32 frame_rate;
++	u64 pixel_rate;
++	u64 stream_data_rate;
++	u16 sram_gran_shift;
++	u16 sram_gran_size;
++	struct list_head stream_node;
++};
++
++struct ipu6_isys_video {
++	struct ipu6_isys_queue aq;
++	/* Serialise access to other fields in the struct. */
++	struct mutex mutex;
++	struct media_pad pad;
++	struct video_device vdev;
++	struct v4l2_pix_format_mplane mpix;
++	const struct ipu6_isys_pixelformat *pfmt;
++	struct ipu6_isys *isys;
++	struct ipu6_isys_stream *stream;
++	unsigned int streaming;
++	struct video_stream_watermark watermark;
++	u32 source_stream;
++	u8 vc;
++	u8 dt;
++};
++
++#define ipu6_isys_queue_to_video(__aq) \
++	container_of(__aq, struct ipu6_isys_video, aq)
++
++extern const struct ipu6_isys_pixelformat ipu6_isys_pfmts[];
++extern const struct ipu6_isys_pixelformat ipu6_isys_pfmts_packed[];
++
++int ipu6_isys_vidioc_querycap(struct file *file, void *fh,
++			      struct v4l2_capability *cap);
++
++int ipu6_isys_vidioc_enum_fmt(struct file *file, void *fh,
++			      struct v4l2_fmtdesc *f);
++int ipu6_isys_video_prepare_stream(struct ipu6_isys_video *av,
++				   struct media_entity *source_entity,
++				   int nr_queues);
++int ipu6_isys_video_set_streaming(struct ipu6_isys_video *av, int state,
++				  struct ipu6_isys_buffer_list *bl);
++int ipu6_isys_fw_open(struct ipu6_isys *isys);
++void ipu6_isys_fw_close(struct ipu6_isys *isys);
++int ipu6_isys_setup_video(struct ipu6_isys_video *av,
++			  struct media_entity **source_entity, int *nr_queues);
++int ipu6_isys_video_init(struct ipu6_isys_video *av);
++void ipu6_isys_video_cleanup(struct ipu6_isys_video *av);
++void ipu6_isys_put_stream(struct ipu6_isys_stream *stream);
++struct ipu6_isys_stream *
++ipu6_isys_query_stream_by_handle(struct ipu6_isys *isys, u8 stream_handle);
++struct ipu6_isys_stream *
++ipu6_isys_query_stream_by_source(struct ipu6_isys *isys, int source, u8 vc);
++
++void ipu6_isys_configure_stream_watermark(struct ipu6_isys_video *av,
++					  bool state);
++void ipu6_isys_update_stream_watermark(struct ipu6_isys_video *av, bool state);
++
++#endif /* IPU6_ISYS_VIDEO_H */
+-- 
+2.43.2
+
+
+From cc79447bab87ce8c498b0e7a5f849c7d4f6262c0 Mon Sep 17 00:00:00 2001
+From: Bingbu Cao <bingbu.cao@intel.com>
+Date: Thu, 11 Jan 2024 14:55:26 +0800
+Subject: [PATCH 19/33] media: add Kconfig and Makefile for IPU6
+
+Add IPU6 support in Kconfig and Makefile, with this patch you can
+build the Intel IPU6 and input system modules by select the
+CONFIG_VIDEO_INTEL_IPU6 in config.
+
+Signed-off-by: Bingbu Cao <bingbu.cao@intel.com>
+Signed-off-by: Andreas Helbech Kleist <andreaskleist@gmail.com>
+---
+ drivers/media/pci/intel/Kconfig       |  1 +
+ drivers/media/pci/intel/Makefile      |  1 +
+ drivers/media/pci/intel/ipu6/Kconfig  | 17 +++++++++++++++++
+ drivers/media/pci/intel/ipu6/Makefile | 23 +++++++++++++++++++++++
+ 4 files changed, 42 insertions(+)
+ create mode 100644 drivers/media/pci/intel/ipu6/Kconfig
+ create mode 100644 drivers/media/pci/intel/ipu6/Makefile
+
+diff --git a/drivers/media/pci/intel/Kconfig b/drivers/media/pci/intel/Kconfig
+index ee4684159d3d..04cb3d253486 100644
+--- a/drivers/media/pci/intel/Kconfig
++++ b/drivers/media/pci/intel/Kconfig
+@@ -1,6 +1,7 @@
+ # SPDX-License-Identifier: GPL-2.0-only
+ 
+ source "drivers/media/pci/intel/ipu3/Kconfig"
++source "drivers/media/pci/intel/ipu6/Kconfig"
+ source "drivers/media/pci/intel/ivsc/Kconfig"
+ 
+ config IPU_BRIDGE
+diff --git a/drivers/media/pci/intel/Makefile b/drivers/media/pci/intel/Makefile
+index f199a97e1d78..3a2cc6567159 100644
+--- a/drivers/media/pci/intel/Makefile
++++ b/drivers/media/pci/intel/Makefile
+@@ -5,3 +5,4 @@
+ obj-$(CONFIG_IPU_BRIDGE) += ipu-bridge.o
+ obj-y	+= ipu3/
+ obj-y	+= ivsc/
++obj-$(CONFIG_VIDEO_INTEL_IPU6)	+= ipu6/
+diff --git a/drivers/media/pci/intel/ipu6/Kconfig b/drivers/media/pci/intel/ipu6/Kconfig
+new file mode 100644
+index 000000000000..5cb4f3c2d59f
+--- /dev/null
++++ b/drivers/media/pci/intel/ipu6/Kconfig
+@@ -0,0 +1,17 @@
++config VIDEO_INTEL_IPU6
++	tristate "Intel IPU6 driver"
++	depends on ACPI || COMPILE_TEST
++	depends on MEDIA_SUPPORT
++	depends on MEDIA_PCI_SUPPORT
++	depends on X86 && X86_64
++	select IOMMU_IOVA
++	select VIDEO_V4L2_SUBDEV_API
++	select VIDEOBUF2_DMA_CONTIG
++	select V4L2_FWNODE
++	select IPU_BRIDGE
++	help
++	  This is the 6th Gen Intel Image Processing Unit, found in Intel SoCs
++	  and used for capturing images and video from camera sensors.
++
++	  To compile this driver, say Y here! It contains 2 modules -
++	  intel_ipu6 and intel_ipu6_isys.
+diff --git a/drivers/media/pci/intel/ipu6/Makefile b/drivers/media/pci/intel/ipu6/Makefile
+new file mode 100644
+index 000000000000..a821b0a1567f
+--- /dev/null
++++ b/drivers/media/pci/intel/ipu6/Makefile
+@@ -0,0 +1,23 @@
++# SPDX-License-Identifier: GPL-2.0-only
++
++intel-ipu6-y			:= ipu6.o \
++				ipu6-bus.o \
++				ipu6-dma.o \
++				ipu6-mmu.o \
++				ipu6-buttress.o \
++				ipu6-cpd.o \
++				ipu6-fw-com.o
++
++obj-$(CONFIG_VIDEO_INTEL_IPU6)	+= intel-ipu6.o
++
++intel-ipu6-isys-y		:= ipu6-isys.o \
++				ipu6-isys-csi2.o \
++				ipu6-fw-isys.o \
++				ipu6-isys-video.o \
++				ipu6-isys-queue.o \
++				ipu6-isys-subdev.o \
++				ipu6-isys-mcd-phy.o \
++				ipu6-isys-jsl-phy.o \
++				ipu6-isys-dwc-phy.o
++
++obj-$(CONFIG_VIDEO_INTEL_IPU6)	+= intel-ipu6-isys.o
+-- 
+2.43.2
+
+
+From edc6bed6991727e64f1eb60c0392403c39b96ba4 Mon Sep 17 00:00:00 2001
+From: Bingbu Cao <bingbu.cao@intel.com>
+Date: Thu, 11 Jan 2024 14:55:27 +0800
+Subject: [PATCH 20/33] MAINTAINERS: add maintainers for Intel IPU6 input
+ system driver
+
+Update MAINTAINERS file for Intel IPU6 input system driver.
+
+Signed-off-by: Bingbu Cao <bingbu.cao@intel.com>
+---
+ MAINTAINERS | 10 ++++++++++
+ 1 file changed, 10 insertions(+)
+
+diff --git a/MAINTAINERS b/MAINTAINERS
+index 1aabf1c15bb3..5346d472cb0f 100644
+--- a/MAINTAINERS
++++ b/MAINTAINERS
+@@ -10899,6 +10899,16 @@ F:	Documentation/admin-guide/media/ipu3_rcb.svg
+ F:	Documentation/userspace-api/media/v4l/metafmt-intel-ipu3.rst
+ F:	drivers/staging/media/ipu3/
+ 
++INTEL IPU6 INPUT SYSTEM DRIVER
++M:	Sakari Ailus <sakari.ailus@linux.intel.com>
++M:	Bingbu Cao <bingbu.cao@intel.com>
++R:	Tianshu Qiu <tian.shu.qiu@intel.com>
++L:	linux-media@vger.kernel.org
++S:	Maintained
++T:	git git://linuxtv.org/media_tree.git
++F:	Documentation/admin-guide/media/ipu6-isys.rst
++F:	drivers/media/pci/intel/ipu6/
++
+ INTEL ISHTP ECLITE DRIVER
+ M:	Sumesh K Naduvalath <sumesh.k.naduvalath@intel.com>
+ L:	platform-driver-x86@vger.kernel.org
+-- 
+2.43.2
+
+
+From a12041e5f7fb32b93669f19b579bc1940a026bbe Mon Sep 17 00:00:00 2001
+From: Bingbu Cao <bingbu.cao@intel.com>
+Date: Thu, 11 Jan 2024 14:55:28 +0800
+Subject: [PATCH 21/33] Documentation: add Intel IPU6 ISYS driver admin-guide
+ doc
+
+This document mainly describe the functionality of IPU6 and
+IPU6 isys driver, and gives an example that how user can do
+imaging capture with tools.
+
+Signed-off-by: Bingbu Cao <bingbu.cao@intel.com>
+---
+ Documentation/admin-guide/media/ipu6-isys.rst | 158 ++++++++++++++++
+ .../admin-guide/media/ipu6_isys_graph.svg     | 174 ++++++++++++++++++
+ .../admin-guide/media/v4l-drivers.rst         |   1 +
+ 3 files changed, 333 insertions(+)
+ create mode 100644 Documentation/admin-guide/media/ipu6-isys.rst
+ create mode 100644 Documentation/admin-guide/media/ipu6_isys_graph.svg
+
+diff --git a/Documentation/admin-guide/media/ipu6-isys.rst b/Documentation/admin-guide/media/ipu6-isys.rst
+new file mode 100644
+index 000000000000..5e78ab88c649
+--- /dev/null
++++ b/Documentation/admin-guide/media/ipu6-isys.rst
+@@ -0,0 +1,158 @@
++.. SPDX-License-Identifier: GPL-2.0
++
++.. include:: <isonum.txt>
++
++========================================================
++Intel Image Processing Unit 6 (IPU6) Input System driver
++========================================================
++
++Copyright |copy| 2023 Intel Corporation
++
++Introduction
++============
++
++This file documents the Intel IPU6 (6th generation Image Processing Unit)
++Input System (MIPI CSI2 receiver) drivers located under
++drivers/media/pci/intel/ipu6.
++
++The Intel IPU6 can be found in certain Intel Chipsets but not in all SKUs:
++
++* TigerLake
++* JasperLake
++* AlderLake
++* RaptorLake
++* MeteorLake
++
++Intel IPU6 is made up of two components - Input System (ISYS) and Processing
++System (PSYS).
++
++The Input System mainly works as MIPI CSI2 receiver which receives and
++processes the imaging data from the sensors and outputs the frames to memory.
++
++There are 2 driver modules - intel_ipu6 and intel_ipu6_isys. intel_ipu6 is an
++IPU6 common driver which does PCI configuration, firmware loading and parsing,
++firmware authentication, DMA mapping and IPU-MMU (internal Memory mapping Unit)
++configuration. intel_ipu6_isys implements V4L2, Media Controller and V4L2
++sub-device interfaces. The IPU6 ISYS driver supports camera sensors connected
++to the IPU6 ISYS through V4L2 sub-device sensor drivers.
++
++.. Note:: See Documentation/driver-api/media/drivers/ipu6.rst for more
++	  information about the IPU6 hardware.
++
++
++Input system driver
++===================
++
++The input System driver mainly configures CSI2 DPHY, constructs the firmware
++stream configuration, sends commands to firmware, gets response from hardware
++and firmware and then returns buffers to user.
++The ISYS is represented as several V4L2 sub-devices - 'Intel IPU6 CSI2 $port',
++which provide V4L2 subdev interfaces to the user space, there are also several
++video nodes for each CSI-2 stream capture - 'Intel IPU6 ISYS capture $num' which
++provide interface to user to set formats, queue buffers and streaming.
++
++.. kernel-figure::  ipu6_isys_graph.svg
++   :alt: ipu6 isys media graph with multiple streams support
++
++   ipu6 isys media graph with multiple streams support
++
++Capturing frames by IPU6 ISYS
++-----------------------------
++
++IPU6 ISYS is used to capture frames from the camera sensors connected to the
++CSI2 ports. The supported input formats of ISYS are listed in table below:
++
++.. tabularcolumns:: |p{0.8cm}|p{4.0cm}|p{4.0cm}|
++
++.. flat-table::
++    :header-rows: 1
++
++    * - IPU6 ISYS supported input formats
++
++    * - RGB565, RGB888
++
++    * - UYVY8, YUYV8
++
++    * - RAW8, RAW10, RAW12
++
++.. _ipu6_isys_capture_examples:
++
++Examples
++~~~~~~~~
++Here is an example of IPU6 ISYS raw capture on Dell XPS 9315 laptop. On this
++machine, ov01a10 sensor is connected to IPU ISYS CSI2 port 2, which can
++generate images at sBGGR10 with resolution 1280x800.
++
++Using the media controller APIs, we can configure ov01a10 sensor by
++media-ctl [#f1]_ and yavta [#f2]_ to transmit frames to IPU6 ISYS.
++
++.. code-block:: none
++
++    # Example 1 capture frame from ov01a10 camera sensor
++    # This example assumes /dev/media0 as the IPU ISYS media device
++    export MDEV=/dev/media0
++
++    # Establish the link for the media devices using media-ctl
++    media-ctl -d $MDEV -l "\"ov01a10 3-0036\":0 -> \"Intel IPU6 CSI2 2\":0[1]"
++
++    # Set the format for the media devices
++    media-ctl -d $MDEV -V "ov01a10:0 [fmt:SBGGR10/1280x800]"
++    media-ctl -d $MDEV -V "Intel IPU6 CSI2 2:0 [fmt:SBGGR10/1280x800]"
++    media-ctl -d $MDEV -V "Intel IPU6 CSI2 2:1 [fmt:SBGGR10/1280x800]"
++
++Once the media pipeline is configured, desired sensor specific settings
++(such as exposure and gain settings) can be set, using the yavta tool.
++
++e.g
++
++.. code-block:: none
++
++    # and that ov01a10 sensor is connected to i2c bus 3 with address 0x36
++    export SDEV=$(media-ctl -d $MDEV -e "ov01a10 3-0036")
++
++    yavta -w 0x009e0903 400 $SDEV
++    yavta -w 0x009e0913 1000 $SDEV
++    yavta -w 0x009e0911 2000 $SDEV
++
++Once the desired sensor settings are set, frame captures can be done as below.
++
++e.g
++
++.. code-block:: none
++
++    yavta --data-prefix -u -c10 -n5 -I -s 1280x800 --file=/tmp/frame-#.bin \
++          -f SBGGR10 $(media-ctl -d $MDEV -e "Intel IPU6 ISYS Capture 0")
++
++With the above command, 10 frames are captured at 1280x800 resolution with
++sBGGR10 format. The captured frames are available as /tmp/frame-#.bin files.
++
++Here is another example of IPU6 ISYS RAW and metadata capture from camera
++sensor ov2740 on Lenovo X1 Yoga laptop.
++
++.. code-block:: none
++
++    media-ctl -l "\"ov2740 14-0036\":0 -> \"Intel IPU6 CSI2 1\":0[1]"
++    media-ctl -l "\"Intel IPU6 CSI2 1\":1 -> \"Intel IPU6 ISYS Capture 0\":0[5]"
++    media-ctl -l "\"Intel IPU6 CSI2 1\":2 -> \"Intel IPU6 ISYS Capture 1\":0[5]"
++
++    # set routing
++    media-ctl -v -R "\"Intel IPU6 CSI2 1\" [0/0->1/0[1],0/1->2/1[1]]"
++
++    media-ctl -v "\"Intel IPU6 CSI2 1\":0/0 [fmt:SGRBG10/1932x1092]"
++    media-ctl -v "\"Intel IPU6 CSI2 1\":0/1 [fmt:GENERIC_8/97x1]"
++    media-ctl -v "\"Intel IPU6 CSI2 1\":1/0 [fmt:SGRBG10/1932x1092]"
++    media-ctl -v "\"Intel IPU6 CSI2 1\":2/1 [fmt:GENERIC_8/97x1]"
++
++    CAPTURE_DEV=$(media-ctl -e "Intel IPU6 ISYS Capture 0")
++    ./yavta --data-prefix -c100 -n5 -I -s1932x1092 --file=/tmp/frame-#.bin \
++    -f SGRBG10 ${CAPTURE_DEV}
++
++    CAPTURE_META=$(media-ctl -e "Intel IPU6 ISYS Capture 1")
++    ./yavta --data-prefix -c100 -n5 -I -s97x1 -B meta-capture \
++    --file=/tmp/meta-#.bin -f GENERIC_8 ${CAPTURE_META}
++
++References
++==========
++
++.. [#f1] https://git.ideasonboard.org/?p=media-ctl.git;a=summary
++.. [#f2] https://git.ideasonboard.org/yavta.git
+diff --git a/Documentation/admin-guide/media/ipu6_isys_graph.svg b/Documentation/admin-guide/media/ipu6_isys_graph.svg
+new file mode 100644
+index 000000000000..707747c75280
+--- /dev/null
++++ b/Documentation/admin-guide/media/ipu6_isys_graph.svg
+@@ -0,0 +1,174 @@
++<?xml version="1.0" encoding="UTF-8" standalone="no"?>
++<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
++ "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
++<!-- Generated by graphviz version 2.38.0 (20140413.2041)
++ -->
++<!-- Title: board Pages: 1 -->
++<svg width="559pt" height="810pt"
++ viewBox="0.00 0.00 559.00 809.50" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
++<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 805.5)">
++<title>board</title>
++<polygon fill="white" stroke="none" points="-4,4 -4,-805.5 555,-805.5 555,4 -4,4"/>
++<!-- n00000001 -->
++<g id="node1" class="node"><title>n00000001</title>
++<polygon fill="#66cd00" stroke="black" points="551,-192.5 387,-192.5 387,-154.5 551,-154.5 551,-192.5"/>
++<text text-anchor="middle" x="469" y="-177.3" font-family="Times,serif" font-size="14.00">Intel IPU6 ISYS Capture 0</text>
++<text text-anchor="middle" x="469" y="-162.3" font-family="Times,serif" font-size="14.00">/dev/video0</text>
++</g>
++<!-- n00000002 -->
++<g id="node2" class="node"><title>n00000002</title>
++<polygon fill="#66cd00" stroke="black" points="551,-395.5 387,-395.5 387,-357.5 551,-357.5 551,-395.5"/>
++<text text-anchor="middle" x="469" y="-380.3" font-family="Times,serif" font-size="14.00">Intel IPU6 ISYS Capture 1</text>
++<text text-anchor="middle" x="469" y="-365.3" font-family="Times,serif" font-size="14.00">/dev/video1</text>
++</g>
++<!-- n00000003 -->
++<g id="node3" class="node"><title>n00000003</title>
++<polygon fill="#66cd00" stroke="black" points="551,-598.5 387,-598.5 387,-560.5 551,-560.5 551,-598.5"/>
++<text text-anchor="middle" x="469" y="-583.3" font-family="Times,serif" font-size="14.00">Intel IPU6 ISYS Capture 2</text>
++<text text-anchor="middle" x="469" y="-568.3" font-family="Times,serif" font-size="14.00">/dev/video2</text>
++</g>
++<!-- n00000004 -->
++<g id="node4" class="node"><title>n00000004</title>
++<polygon fill="#66cd00" stroke="black" points="551,-801.5 387,-801.5 387,-763.5 551,-763.5 551,-801.5"/>
++<text text-anchor="middle" x="469" y="-786.3" font-family="Times,serif" font-size="14.00">Intel IPU6 ISYS Capture 3</text>
++<text text-anchor="middle" x="469" y="-771.3" font-family="Times,serif" font-size="14.00">/dev/video3</text>
++</g>
++<!-- n0000007d -->
++<g id="node5" class="node"><title>n0000007d</title>
++<path fill="#ffb90f" stroke="black" d="M201,-0.5C201,-0.5 339,-0.5 339,-0.5 345,-0.5 351,-6.5 351,-12.5 351,-12.5 351,-172.5 351,-172.5 351,-178.5 345,-184.5 339,-184.5 339,-184.5 201,-184.5 201,-184.5 195,-184.5 189,-178.5 189,-172.5 189,-172.5 189,-12.5 189,-12.5 189,-6.5 195,-0.5 201,-0.5"/>
++<text text-anchor="middle" x="200.5" y="-88.8" font-family="Times,serif" font-size="14.00">0</text>
++<polyline fill="none" stroke="black" points="212,-0.5 212,-184.5 "/>
++<text text-anchor="middle" x="270" y="-96.3" font-family="Times,serif" font-size="14.00">Intel IPU6 CSI2 0</text>
++<text text-anchor="middle" x="270" y="-81.3" font-family="Times,serif" font-size="14.00">/dev/v4l&#45;subdev0</text>
++<polyline fill="none" stroke="black" points="328,-0.5 328,-184.5 "/>
++<text text-anchor="middle" x="339.5" y="-169.3" font-family="Times,serif" font-size="14.00">1</text>
++<polyline fill="none" stroke="black" points="328,-161.5 351,-161.5 "/>
++<text text-anchor="middle" x="339.5" y="-146.3" font-family="Times,serif" font-size="14.00">2</text>
++<polyline fill="none" stroke="black" points="328,-138.5 351,-138.5 "/>
++<text text-anchor="middle" x="339.5" y="-123.3" font-family="Times,serif" font-size="14.00">3</text>
++<polyline fill="none" stroke="black" points="328,-115.5 351,-115.5 "/>
++<text text-anchor="middle" x="339.5" y="-100.3" font-family="Times,serif" font-size="14.00">4</text>
++<polyline fill="none" stroke="black" points="328,-92.5 351,-92.5 "/>
++<text text-anchor="middle" x="339.5" y="-77.3" font-family="Times,serif" font-size="14.00">5</text>
++<polyline fill="none" stroke="black" points="328,-69.5 351,-69.5 "/>
++<text text-anchor="middle" x="339.5" y="-54.3" font-family="Times,serif" font-size="14.00">6</text>
++<polyline fill="none" stroke="black" points="328,-46.5 351,-46.5 "/>
++<text text-anchor="middle" x="339.5" y="-31.3" font-family="Times,serif" font-size="14.00">7</text>
++<polyline fill="none" stroke="black" points="328,-23.5 351,-23.5 "/>
++<text text-anchor="middle" x="339.5" y="-8.3" font-family="Times,serif" font-size="14.00">8</text>
++</g>
++<!-- n0000007d&#45;&gt;n00000001 -->
++<g id="edge1" class="edge"><title>n0000007d:port1&#45;&gt;n00000001</title>
++<path fill="none" stroke="black" stroke-dasharray="5,2" d="M351,-173.5C359.322,-173.5 367.976,-173.5 376.644,-173.5"/>
++<polygon fill="black" stroke="black" points="376.807,-177 386.807,-173.5 376.807,-170 376.807,-177"/>
++</g>
++<!-- n00000087 -->
++<g id="node6" class="node"><title>n00000087</title>
++<path fill="#ffb90f" stroke="black" d="M201,-203.5C201,-203.5 339,-203.5 339,-203.5 345,-203.5 351,-209.5 351,-215.5 351,-215.5 351,-375.5 351,-375.5 351,-381.5 345,-387.5 339,-387.5 339,-387.5 201,-387.5 201,-387.5 195,-387.5 189,-381.5 189,-375.5 189,-375.5 189,-215.5 189,-215.5 189,-209.5 195,-203.5 201,-203.5"/>
++<text text-anchor="middle" x="200.5" y="-291.8" font-family="Times,serif" font-size="14.00">0</text>
++<polyline fill="none" stroke="black" points="212,-203.5 212,-387.5 "/>
++<text text-anchor="middle" x="270" y="-299.3" font-family="Times,serif" font-size="14.00">Intel IPU6 CSI2 1</text>
++<text text-anchor="middle" x="270" y="-284.3" font-family="Times,serif" font-size="14.00">/dev/v4l&#45;subdev1</text>
++<polyline fill="none" stroke="black" points="328,-203.5 328,-387.5 "/>
++<text text-anchor="middle" x="339.5" y="-372.3" font-family="Times,serif" font-size="14.00">1</text>
++<polyline fill="none" stroke="black" points="328,-364.5 351,-364.5 "/>
++<text text-anchor="middle" x="339.5" y="-349.3" font-family="Times,serif" font-size="14.00">2</text>
++<polyline fill="none" stroke="black" points="328,-341.5 351,-341.5 "/>
++<text text-anchor="middle" x="339.5" y="-326.3" font-family="Times,serif" font-size="14.00">3</text>
++<polyline fill="none" stroke="black" points="328,-318.5 351,-318.5 "/>
++<text text-anchor="middle" x="339.5" y="-303.3" font-family="Times,serif" font-size="14.00">4</text>
++<polyline fill="none" stroke="black" points="328,-295.5 351,-295.5 "/>
++<text text-anchor="middle" x="339.5" y="-280.3" font-family="Times,serif" font-size="14.00">5</text>
++<polyline fill="none" stroke="black" points="328,-272.5 351,-272.5 "/>
++<text text-anchor="middle" x="339.5" y="-257.3" font-family="Times,serif" font-size="14.00">6</text>
++<polyline fill="none" stroke="black" points="328,-249.5 351,-249.5 "/>
++<text text-anchor="middle" x="339.5" y="-234.3" font-family="Times,serif" font-size="14.00">7</text>
++<polyline fill="none" stroke="black" points="328,-226.5 351,-226.5 "/>
++<text text-anchor="middle" x="339.5" y="-211.3" font-family="Times,serif" font-size="14.00">8</text>
++</g>
++<!-- n00000087&#45;&gt;n00000002 -->
++<g id="edge2" class="edge"><title>n00000087:port1&#45;&gt;n00000002</title>
++<path fill="none" stroke="black" stroke-dasharray="5,2" d="M351,-376.5C359.322,-376.5 367.976,-376.5 376.644,-376.5"/>
++<polygon fill="black" stroke="black" points="376.807,-380 386.807,-376.5 376.807,-373 376.807,-380"/>
++</g>
++<!-- n00000091 -->
++<g id="node7" class="node"><title>n00000091</title>
++<path fill="#ffb90f" stroke="black" d="M201,-406.5C201,-406.5 339,-406.5 339,-406.5 345,-406.5 351,-412.5 351,-418.5 351,-418.5 351,-578.5 351,-578.5 351,-584.5 345,-590.5 339,-590.5 339,-590.5 201,-590.5 201,-590.5 195,-590.5 189,-584.5 189,-578.5 189,-578.5 189,-418.5 189,-418.5 189,-412.5 195,-406.5 201,-406.5"/>
++<text text-anchor="middle" x="200.5" y="-494.8" font-family="Times,serif" font-size="14.00">0</text>
++<polyline fill="none" stroke="black" points="212,-406.5 212,-590.5 "/>
++<text text-anchor="middle" x="270" y="-502.3" font-family="Times,serif" font-size="14.00">Intel IPU6 CSI2 2</text>
++<text text-anchor="middle" x="270" y="-487.3" font-family="Times,serif" font-size="14.00">/dev/v4l&#45;subdev2</text>
++<polyline fill="none" stroke="black" points="328,-406.5 328,-590.5 "/>
++<text text-anchor="middle" x="339.5" y="-575.3" font-family="Times,serif" font-size="14.00">1</text>
++<polyline fill="none" stroke="black" points="328,-567.5 351,-567.5 "/>
++<text text-anchor="middle" x="339.5" y="-552.3" font-family="Times,serif" font-size="14.00">2</text>
++<polyline fill="none" stroke="black" points="328,-544.5 351,-544.5 "/>
++<text text-anchor="middle" x="339.5" y="-529.3" font-family="Times,serif" font-size="14.00">3</text>
++<polyline fill="none" stroke="black" points="328,-521.5 351,-521.5 "/>
++<text text-anchor="middle" x="339.5" y="-506.3" font-family="Times,serif" font-size="14.00">4</text>
++<polyline fill="none" stroke="black" points="328,-498.5 351,-498.5 "/>
++<text text-anchor="middle" x="339.5" y="-483.3" font-family="Times,serif" font-size="14.00">5</text>
++<polyline fill="none" stroke="black" points="328,-475.5 351,-475.5 "/>
++<text text-anchor="middle" x="339.5" y="-460.3" font-family="Times,serif" font-size="14.00">6</text>
++<polyline fill="none" stroke="black" points="328,-452.5 351,-452.5 "/>
++<text text-anchor="middle" x="339.5" y="-437.3" font-family="Times,serif" font-size="14.00">7</text>
++<polyline fill="none" stroke="black" points="328,-429.5 351,-429.5 "/>
++<text text-anchor="middle" x="339.5" y="-414.3" font-family="Times,serif" font-size="14.00">8</text>
++</g>
++<!-- n00000091&#45;&gt;n00000003 -->
++<g id="edge3" class="edge"><title>n00000091:port1&#45;&gt;n00000003</title>
++<path fill="none" stroke="black" d="M351,-579.5C359.322,-579.5 367.976,-579.5 376.644,-579.5"/>
++<polygon fill="black" stroke="black" points="376.807,-583 386.807,-579.5 376.807,-576 376.807,-583"/>
++</g>
++<!-- n0000009b -->
++<g id="node8" class="node"><title>n0000009b</title>
++<path fill="#ffb90f" stroke="black" d="M201,-609.5C201,-609.5 339,-609.5 339,-609.5 345,-609.5 351,-615.5 351,-621.5 351,-621.5 351,-781.5 351,-781.5 351,-787.5 345,-793.5 339,-793.5 339,-793.5 201,-793.5 201,-793.5 195,-793.5 189,-787.5 189,-781.5 189,-781.5 189,-621.5 189,-621.5 189,-615.5 195,-609.5 201,-609.5"/>
++<text text-anchor="middle" x="200.5" y="-697.8" font-family="Times,serif" font-size="14.00">0</text>
++<polyline fill="none" stroke="black" points="212,-609.5 212,-793.5 "/>
++<text text-anchor="middle" x="270" y="-705.3" font-family="Times,serif" font-size="14.00">Intel IPU6 CSI2 3</text>
++<text text-anchor="middle" x="270" y="-690.3" font-family="Times,serif" font-size="14.00">/dev/v4l&#45;subdev3</text>
++<polyline fill="none" stroke="black" points="328,-609.5 328,-793.5 "/>
++<text text-anchor="middle" x="339.5" y="-778.3" font-family="Times,serif" font-size="14.00">1</text>
++<polyline fill="none" stroke="black" points="328,-770.5 351,-770.5 "/>
++<text text-anchor="middle" x="339.5" y="-755.3" font-family="Times,serif" font-size="14.00">2</text>
++<polyline fill="none" stroke="black" points="328,-747.5 351,-747.5 "/>
++<text text-anchor="middle" x="339.5" y="-732.3" font-family="Times,serif" font-size="14.00">3</text>
++<polyline fill="none" stroke="black" points="328,-724.5 351,-724.5 "/>
++<text text-anchor="middle" x="339.5" y="-709.3" font-family="Times,serif" font-size="14.00">4</text>
++<polyline fill="none" stroke="black" points="328,-701.5 351,-701.5 "/>
++<text text-anchor="middle" x="339.5" y="-686.3" font-family="Times,serif" font-size="14.00">5</text>
++<polyline fill="none" stroke="black" points="328,-678.5 351,-678.5 "/>
++<text text-anchor="middle" x="339.5" y="-663.3" font-family="Times,serif" font-size="14.00">6</text>
++<polyline fill="none" stroke="black" points="328,-655.5 351,-655.5 "/>
++<text text-anchor="middle" x="339.5" y="-640.3" font-family="Times,serif" font-size="14.00">7</text>
++<polyline fill="none" stroke="black" points="328,-632.5 351,-632.5 "/>
++<text text-anchor="middle" x="339.5" y="-617.3" font-family="Times,serif" font-size="14.00">8</text>
++</g>
++<!-- n0000009b&#45;&gt;n00000004 -->
++<g id="edge4" class="edge"><title>n0000009b:port1&#45;&gt;n00000004</title>
++<path fill="none" stroke="black" stroke-dasharray="5,2" d="M351,-782.5C359.322,-782.5 367.976,-782.5 376.644,-782.5"/>
++<polygon fill="black" stroke="black" points="376.807,-786 386.807,-782.5 376.807,-779 376.807,-786"/>
++</g>
++<!-- n00000865 -->
++<g id="node9" class="node"><title>n00000865</title>
++<path fill="cornflowerblue" stroke="black" d="M12,-479.5C12,-479.5 141,-479.5 141,-479.5 147,-479.5 153,-485.5 153,-491.5 153,-491.5 153,-505.5 153,-505.5 153,-511.5 147,-517.5 141,-517.5 141,-517.5 12,-517.5 12,-517.5 6,-517.5 0,-511.5 0,-505.5 0,-505.5 0,-491.5 0,-491.5 0,-485.5 6,-479.5 12,-479.5"/>
++<text text-anchor="middle" x="10" y="-494.8" font-family="Times,serif" font-size="14.00"> </text>
++<polyline fill="none" stroke="black" points="20,-479.5 20,-517.5 "/>
++<text text-anchor="middle" x="75" y="-502.3" font-family="Times,serif" font-size="14.00">ov01a10 3&#45;0036</text>
++<text text-anchor="middle" x="75" y="-487.3" font-family="Times,serif" font-size="14.00">/dev/v4l&#45;subdev4</text>
++<polyline fill="none" stroke="black" points="130,-479.5 130,-517.5 "/>
++<text text-anchor="middle" x="141.5" y="-494.8" font-family="Times,serif" font-size="14.00">0</text>
++</g>
++<!-- n00000865&#45;&gt;n00000091 -->
++<g id="edge5" class="edge"><title>n00000865:port0&#45;&gt;n00000091:port0</title>
++<path fill="none" stroke="black" d="M153,-498.5C165,-498.5 170.25,-498.5 178.875,-498.5"/>
++<polygon fill="black" stroke="black" points="179,-502 189,-498.5 179,-495 179,-502"/>
++</g>
++<!-- n00000866 -->
++<!-- n00000866&#45;&gt;n0000007d -->
++<!-- n00000867 -->
++<!-- n00000867&#45;&gt;n00000087 -->
++<!-- n00000868 -->
++<!-- n00000868&#45;&gt;n0000009b -->
++</g>
++</svg>
+diff --git a/Documentation/admin-guide/media/v4l-drivers.rst b/Documentation/admin-guide/media/v4l-drivers.rst
+index f4bb2605f07e..4120eded9a13 100644
+--- a/Documentation/admin-guide/media/v4l-drivers.rst
++++ b/Documentation/admin-guide/media/v4l-drivers.rst
+@@ -16,6 +16,7 @@ Video4Linux (V4L) driver-specific documentation
+ 	imx
+ 	imx7
+ 	ipu3
++	ipu6-isys
+ 	ivtv
+ 	mgb4
+ 	omap3isp
+-- 
+2.43.2
+
+
+From 3e80683ecc9ffe38fdf6e6232089794b6019816b Mon Sep 17 00:00:00 2001
+From: Bingbu Cao <bingbu.cao@intel.com>
+Date: Thu, 11 Jan 2024 14:55:29 +0800
+Subject: [PATCH 22/33] Documentation: add documentation of Intel IPU6 driver
+ and hardware overview
+
+Add a documentation for an overview of IPU6 hardware and describe the main
+the components of IPU6 driver.
+
+Signed-off-by: Bingbu Cao <bingbu.cao@intel.com>
+---
+ .../driver-api/media/drivers/index.rst        |   1 +
+ .../driver-api/media/drivers/ipu6.rst         | 205 ++++++++++++++++++
+ 2 files changed, 206 insertions(+)
+ create mode 100644 Documentation/driver-api/media/drivers/ipu6.rst
+
+diff --git a/Documentation/driver-api/media/drivers/index.rst b/Documentation/driver-api/media/drivers/index.rst
+index c4123a16b5f9..7f6f3dcd5c90 100644
+--- a/Documentation/driver-api/media/drivers/index.rst
++++ b/Documentation/driver-api/media/drivers/index.rst
+@@ -26,6 +26,7 @@ Video4Linux (V4L) drivers
+ 	vimc-devel
+ 	zoran
+ 	ccs/ccs
++	ipu6
+ 
+ 
+ Digital TV drivers
+diff --git a/Documentation/driver-api/media/drivers/ipu6.rst b/Documentation/driver-api/media/drivers/ipu6.rst
+new file mode 100644
+index 000000000000..b6357155c13b
+--- /dev/null
++++ b/Documentation/driver-api/media/drivers/ipu6.rst
+@@ -0,0 +1,205 @@
++.. SPDX-License-Identifier: GPL-2.0
++
++==================
++Intel IPU6 Driver
++==================
++
++Author: Bingbu Cao <bingbu.cao@intel.com>
++
++Overview
++=========
++
++Intel IPU6 is the sixth generation of Intel Image Processing Unit used in some
++Intel Chipsets such as Tiger Lake, Jasper Lake, Alder Lake, Raptor Lake and
++Meteor Lake. IPU6 consists of two major systems: Input System (IS) and
++Processing System (PS). IPU6 are visible on the PCI bus as a single device,
++it can be found by ``lspci``:
++
++``0000:00:05.0 Multimedia controller: Intel Corporation Device xxxx (rev xx)``
++
++IPU6 has a 16 MB BAR in PCI configuration Space for MMIO registers which is
++visible for driver.
++
++Buttress
++=========
++
++The IPU6 is connecting to the system fabric with ``Buttress`` which is enabling
++host driver to control the IPU6, it also allows IPU6 access the system memory to
++store and load frame pixel streams and any other metadata.
++
++``Buttress`` mainly manages several system functionalities - power management,
++interrupt handling, firmware authentication and global timer sync.
++
++IS and PS Power flow
++---------------------------
++
++IPU6 driver initialize the IS and PS power up or down request by setting the
++Buttress frequency control register for IS and PS -
++``IPU6_BUTTRESS_REG_IS_FREQ_CTL`` and ``IPU6_BUTTRESS_REG_PS_FREQ_CTL`` in
++function:
++
++.. c:function:: int ipu6_buttress_power(..., bool on)
++
++Buttress forwards the request to Punit, after Punit execute the power up flow,
++buttress indicates driver that IS or PS is powered up by updating the power
++status registers.
++
++.. Note:: IS power up needs take place prior to PS power up, IS power down needs
++	  take place after PS power down due to hardware limitation.
++
++
++Interrupt
++------------
++
++IPU6 interrupt can be generated as MSI or INTA, interrupt will be triggered
++when IS, PS, Buttress event or error happen, driver can get the interrupt
++cause by reading the interrupt status register ``BUTTRESS_REG_ISR_STATUS``,
++driver firstly clear the irq status and then call specific IS or PS irq handler.
++
++.. c:function:: irqreturn_t ipu6_buttress_isr(int irq, ...)
++
++Security and firmware authentication
++-------------------------------------
++To address the IPU6 firmware security concerns, the IPU6 firmware needs to
++undergo an authentication process before it is allowed to executed on the IPU6
++internal processors. Driver will work with Converged Security Engine (CSE) to
++complete authentication process. CSE is responsible of authenticating the
++IPU6 firmware, the authenticated firmware binary is copied into an isolated
++memory region. Firmware authentication process is implemented by CSE following
++an IPC handshake with driver. There are some Buttress registers used by CSE and
++driver to communicate with each other as IPC messages.
++
++.. c:function:: int ipu6_buttress_authenticate(...)
++
++Global timer sync
++------------------
++IPU driver initiates a Hammock Harbor synchronization flow each time it starts
++camera operation. IPU will synchronizes an internal counter in the Buttress
++with a copy of SoC time, this counter keeps the updated time until camera
++operation is stopped. Driver can use this time counter to calibrate the
++timestamp based on the timestamp in response event from firmware.
++
++.. c:function:: int ipu6_buttress_start_tsc_sync(...)
++
++
++DMA and MMU
++============
++
++IPU6 has its own scalar processor where the firmware run at, it has
++an internal 32-bits virtual address space. IPU6 has MMU address translation
++hardware to allow that scalar process access the internal memory and external
++system memory through IPU6 virtual address. The address translation is
++based on two levels of page lookup tables stored in system memory which are
++maintained by IPU6 driver. IPU6 driver sets the level-1 page table base address
++to MMU register and allow MMU to lookup the page table.
++
++IPU6 driver exports its own DMA operations. Driver will update the page table
++entries for each DMA operation and invalidate the MMU TLB after each unmap and
++free.
++
++.. code-block:: none
++
++    const struct dma_map_ops ipu6_dma_ops = {
++	   .alloc = ipu6_dma_alloc,
++	   .free = ipu6_dma_free,
++	   .mmap = ipu6_dma_mmap,
++	   .map_sg = ipu6_dma_map_sg,
++	   .unmap_sg = ipu6_dma_unmap_sg,
++	   ...
++    };
++
++.. Note:: IPU6 MMU works behind IOMMU, so for each IPU6 DMA ops, driver will
++	  call generic PCI DMA ops to ask IOMMU to do the additional mapping
++	  if VT-d enabled.
++
++
++Firmware file format
++=====================
++
++IPU6 release the firmware in Code Partition Directory (CPD) file format. The
++CPD firmware contains a CPD header, several CPD entries and CPD components.
++CPD component includes 3 entries - manifest, metadata and module data. Manifest
++and metadata are defined by CSE and used by CSE for authentication. Module data
++is defined by IPU6 which holds the binary data of firmware called package
++directory. IPU6 driver (``ipu6-cpd.c``) parses and validates the CPD firmware
++file and get the package directory binary data of IPU6 firmware, copy it to
++specific DMA buffer and sets its base address to Buttress ``FW_SOURCE_BASE``
++register, CSE will do authentication for this firmware binary.
++
++
++Syscom interface
++================
++
++IPU6 driver communicates with firmware via syscom ABI. Syscom is an
++inter-processor communication mechanism between IPU scalar processor and CPU.
++There are a number of resources shared between firmware and software.
++A system memory region where the message queues reside, firmware can access the
++memory region via IPU MMU. Syscom queues are FIFO fixed depth queues with
++configurable elements ``token`` (message). There is also a common IPU MMIO
++registers where the queue read and write indices reside. Software and firmware
++work as producer and consumer of tokens in queue, and update the write and read
++indices separately when sending or receiving each message.
++
++IPU6 driver must prepare and configure the number of input and output queues,
++configure the count of tokens per queue and the size of per token before
++initiate and start the communication with firmware, firmware and software must
++use same configurations. IPU6 Buttress has a number of firmware boot parameter
++registers which can be used to store the address of configuration and initiate
++the Syscom state, then driver can request firmware to start and run via setting
++the scalar processor control status register.
++
++
++Input System
++==============
++
++IPU6 input system consists of MIPI D-PHY and several CSI receiver controllers,
++it can capture image pixel data from camera sensors or other MIPI CSI output
++devices.
++
++D-PHYs and CSI-2 ports lane mapping
++-----------------------------------
++
++IPU6 integrates different D-PHY IPs on different SoCs, on Tiger Lake and Alder
++Lake, IPU6 integrates MCD10 D-PHY, IPU6SE on Jasper Lake integrates JSL D-PHY
++and IPU6EP on Meteor Lake integrates a Synopsys DWC D-PHY. There is an adaption
++layer between D-PHY and CSI receiver controller which includes port
++configuration, PHY wrapper or private test interfaces for D-PHY. There are 3
++D-PHY drivers ``ipu6-isys-mcd-phy.c``, ``ipu6-isys-jsl-phy.c`` and
++``ipu6-isys-dwc-phy.c`` program the above 3 D-PHYs in IPU6.
++
++Different IPU6 version has different D-PHY lanes mappings, On Tiger Lake, there
++are 12 data lanes and 8 clock lanes, IPU6 support maximum 8 CSI-2 ports, see
++the ppi mmapping in ``ipu6-isys-mcd-phy.c`` for more information. On Jasper Lake
++and Alder Lake, D-PHY has 8 data lanes and 4 clock lanes, IPU6 support maximum 4
++CSI-2 ports. For Meteor Lake, D-PHY has 12 data lanes and 6 clock lanes, IPU6
++support maximum 6 CSI-2 ports.
++
++.. Note:: Each adjacent CSI ports work as a pair and share the data lanes.
++	  For example, for CSI port 0 and 1, CSI port 0 support maximum 4
++	  data lanes, CSI port 1 support maximum 2 data lanes, CSI port 0
++	  with 2 data lanes can work together with CSI port 1 with 2 data lanes.
++	  If trying to use CSI port 0 with 4 lanes, CSI port 1 will not be
++	  available as the 4 data lanes are shared by CSI port 0 and 1. Same
++	  scenario is also applied for CSI port 2/3, 4/5 and 7/8.
++
++IS firmware ABIs
++----------------
++
++IPU6 firmware define a series of ABIs to software. In general, software firstly
++prepare the stream configuration ``struct ipu6_fw_isys_stream_cfg_data_abi``
++and send the configuration to firmware via sending ``STREAM_OPEN`` command.
++Stream configuration includes input pins and output pins, input pin
++``struct ipu6_fw_isys_input_pin_info_abi`` defines the resolution and data type
++of input source, output pin ``struct ipu6_fw_isys_output_pin_info_abi``
++defines the output resolution, stride and frame format, etc. Once driver get the
++interrupt from firmware that indicates stream open successfully, driver will
++send the ``STREAM_START`` and ``STREAM_CAPTURE`` command to request firmware to
++start capturing image frames. ``STREAM_CAPTURE`` command queues the buffers to
++firmware with ``struct ipu6_fw_isys_frame_buff_set``, software then wait the
++interrupt and response from firmware, ``PIN_DATA_READY`` means data ready
++on specific output pin and then software return the buffers to user.
++
++.. Note:: See :ref:`Examples<ipu6_isys_capture_examples>` about how to do
++	  capture by IPU6 IS driver.
++
++
+-- 
+2.43.2
+
+
+From d883f3386e7185d9404cb25e32df986656a4e82a Mon Sep 17 00:00:00 2001
+From: Bingbu Cao <bingbu.cao@intel.com>
+Date: Thu, 11 Jan 2024 14:55:30 +0800
+Subject: [PATCH 23/33] media: ipu6/isys: support line-based metadata capture
+ support
+
+Some camera sensor can output the embedded data in specific
+data type.  This patch add the support for embedded data capture
+in IPU6 IS driver.
+
+It's based on Sakari's line-based metadata capture support change:
+<URL:https://git.linuxtv.org/sailus/media_tree.git/log/?h=metadata>
+
+Signed-off-by: Hongju Wang <hongju.wang@intel.com>
+Signed-off-by: Bingbu Cao <bingbu.cao@intel.com>
+---
+ drivers/media/pci/intel/ipu6/ipu6-isys-csi2.c |   5 +
+ .../media/pci/intel/ipu6/ipu6-isys-queue.c    |  44 ++--
+ .../media/pci/intel/ipu6/ipu6-isys-subdev.c   |   5 +
+ .../media/pci/intel/ipu6/ipu6-isys-video.c    | 201 +++++++++++++++---
+ .../media/pci/intel/ipu6/ipu6-isys-video.h    |   7 +-
+ 5 files changed, 216 insertions(+), 46 deletions(-)
+
+diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys-csi2.c b/drivers/media/pci/intel/ipu6/ipu6-isys-csi2.c
+index ac9fa3e0d7ab..a6430d531129 100644
+--- a/drivers/media/pci/intel/ipu6/ipu6-isys-csi2.c
++++ b/drivers/media/pci/intel/ipu6/ipu6-isys-csi2.c
+@@ -42,6 +42,11 @@ static const u32 csi2_supported_codes[] = {
+ 	MEDIA_BUS_FMT_SGBRG8_1X8,
+ 	MEDIA_BUS_FMT_SGRBG8_1X8,
+ 	MEDIA_BUS_FMT_SRGGB8_1X8,
++	MEDIA_BUS_FMT_META_8,
++	MEDIA_BUS_FMT_META_10,
++	MEDIA_BUS_FMT_META_12,
++	MEDIA_BUS_FMT_META_16,
++	MEDIA_BUS_FMT_META_24,
+ 	0
+ };
+ 
+diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys-queue.c b/drivers/media/pci/intel/ipu6/ipu6-isys-queue.c
+index 735d2d642d87..15fa7ed22b2f 100644
+--- a/drivers/media/pci/intel/ipu6/ipu6-isys-queue.c
++++ b/drivers/media/pci/intel/ipu6/ipu6-isys-queue.c
+@@ -35,11 +35,14 @@ static int queue_setup(struct vb2_queue *q, unsigned int *num_buffers,
+ 	/* num_planes == 0: we're being called through VIDIOC_REQBUFS */
+ 	if (!*num_planes) {
+ 		use_fmt = true;
+-		*num_planes = av->mpix.num_planes;
++		if (av->vfmt.type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE)
++			*num_planes = av->vfmt.fmt.pix_mp.num_planes;
++		else if (av->vfmt.type == V4L2_BUF_TYPE_META_CAPTURE)
++			*num_planes = 1;
+ 	}
+ 
+ 	for (i = 0; i < *num_planes; i++) {
+-		size = av->mpix.plane_fmt[i].sizeimage;
++		size = ipu6_get_data_size(&av->vfmt, i);
+ 		if (use_fmt) {
+ 			sizes[i] = size;
+ 		} else if (sizes[i] < size) {
+@@ -59,16 +62,17 @@ static int ipu6_isys_buf_prepare(struct vb2_buffer *vb)
+ 	struct ipu6_isys_queue *aq = vb2_queue_to_isys_queue(vb->vb2_queue);
+ 	struct ipu6_isys_video *av = ipu6_isys_queue_to_video(aq);
+ 	struct device *dev = &av->isys->adev->auxdev.dev;
++	u32 bytesperline = ipu6_get_bytes_per_line(&av->vfmt);
++	u32 height = ipu6_get_frame_height(&av->vfmt);
++	u32 size = ipu6_get_data_size(&av->vfmt, 0);
+ 
+ 	dev_dbg(dev, "buffer: %s: configured size %u, buffer size %lu\n",
+-		av->vdev.name, av->mpix.plane_fmt[0].sizeimage,
+-		vb2_plane_size(vb, 0));
++		av->vdev.name, size, vb2_plane_size(vb, 0));
+ 
+-	if (av->mpix.plane_fmt[0].sizeimage > vb2_plane_size(vb, 0))
++	if (size > vb2_plane_size(vb, 0))
+ 		return -EINVAL;
+ 
+-	vb2_set_plane_payload(vb, 0, av->mpix.plane_fmt[0].bytesperline *
+-			      av->mpix.height);
++	vb2_set_plane_payload(vb, 0, bytesperline * height);
+ 	vb->planes[0].data_offset = 0;
+ 
+ 	return 0;
+@@ -437,18 +441,22 @@ static int ipu6_isys_link_fmt_validate(struct ipu6_isys_queue *aq)
+ 		return ret;
+ 	}
+ 
+-	if (format.width != av->mpix.width ||
+-	    format.height != av->mpix.height) {
+-		dev_dbg(dev, "wrong width or height %ux%u (%ux%u expected)\n",
+-			av->mpix.width, av->mpix.height,
+-			format.width, format.height);
++	if (format.width != ipu6_get_frame_width(&av->vfmt) ||
++	    format.height != ipu6_get_frame_height(&av->vfmt)) {
++		dev_err(dev, "wrong width or height %ux%u (%ux%u expected)\n",
++			ipu6_get_frame_width(&av->vfmt),
++			ipu6_get_frame_height(&av->vfmt), format.width,
++			format.height);
+ 		return -EINVAL;
+ 	}
+ 
+-	if (format.field != av->mpix.field) {
+-		dev_dbg(dev, "wrong field value 0x%8.8x (0x%8.8x expected)\n",
+-			av->mpix.field, format.field);
+-		return -EINVAL;
++	if (av->vfmt.type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) {
++		if (format.field != av->vfmt.fmt.pix_mp.field) {
++			dev_dbg(dev,
++				"wrong field value 0x%8.8x (%8.8x expected)\n",
++				av->vfmt.fmt.pix_mp.field, format.field);
++			return -EINVAL;
++		}
+ 	}
+ 
+ 	if (format.code != av->pfmt->code) {
+@@ -531,8 +539,8 @@ static int start_streaming(struct vb2_queue *q, unsigned int count)
+ 	int nr_queues, ret;
+ 
+ 	dev_dbg(dev, "stream: %s: width %u, height %u, css pixelformat %u\n",
+-		av->vdev.name, av->mpix.width, av->mpix.height,
+-		av->pfmt->css_pixelformat);
++		av->vdev.name, ipu6_get_frame_width(&av->vfmt),
++		ipu6_get_frame_height(&av->vfmt), av->pfmt->css_pixelformat);
+ 
+ 	ret = ipu6_isys_setup_video(av, &source_entity, &nr_queues);
+ 	if (ret < 0) {
+diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys-subdev.c b/drivers/media/pci/intel/ipu6/ipu6-isys-subdev.c
+index 510c5ca34f9f..3c9263ac02a3 100644
+--- a/drivers/media/pci/intel/ipu6/ipu6-isys-subdev.c
++++ b/drivers/media/pci/intel/ipu6/ipu6-isys-subdev.c
+@@ -20,25 +20,30 @@ unsigned int ipu6_isys_mbus_code_to_bpp(u32 code)
+ {
+ 	switch (code) {
+ 	case MEDIA_BUS_FMT_RGB888_1X24:
++	case MEDIA_BUS_FMT_META_24:
+ 		return 24;
+ 	case MEDIA_BUS_FMT_RGB565_1X16:
+ 	case MEDIA_BUS_FMT_UYVY8_1X16:
+ 	case MEDIA_BUS_FMT_YUYV8_1X16:
++	case MEDIA_BUS_FMT_META_16:
+ 		return 16;
+ 	case MEDIA_BUS_FMT_SBGGR12_1X12:
+ 	case MEDIA_BUS_FMT_SGBRG12_1X12:
+ 	case MEDIA_BUS_FMT_SGRBG12_1X12:
+ 	case MEDIA_BUS_FMT_SRGGB12_1X12:
++	case MEDIA_BUS_FMT_META_12:
+ 		return 12;
+ 	case MEDIA_BUS_FMT_SBGGR10_1X10:
+ 	case MEDIA_BUS_FMT_SGBRG10_1X10:
+ 	case MEDIA_BUS_FMT_SGRBG10_1X10:
+ 	case MEDIA_BUS_FMT_SRGGB10_1X10:
++	case MEDIA_BUS_FMT_META_10:
+ 		return 10;
+ 	case MEDIA_BUS_FMT_SBGGR8_1X8:
+ 	case MEDIA_BUS_FMT_SGBRG8_1X8:
+ 	case MEDIA_BUS_FMT_SGRBG8_1X8:
+ 	case MEDIA_BUS_FMT_SRGGB8_1X8:
++	case MEDIA_BUS_FMT_META_8:
+ 		return 8;
+ 	default:
+ 		WARN_ON(1);
+diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys-video.c b/drivers/media/pci/intel/ipu6/ipu6-isys-video.c
+index 847eac26bcd6..1a023bf1e1a6 100644
+--- a/drivers/media/pci/intel/ipu6/ipu6-isys-video.c
++++ b/drivers/media/pci/intel/ipu6/ipu6-isys-video.c
+@@ -85,6 +85,11 @@ const struct ipu6_isys_pixelformat ipu6_isys_pfmts[] = {
+ 	 IPU6_FW_ISYS_FRAME_FORMAT_RGB565},
+ 	{V4L2_PIX_FMT_BGR24, 24, 24, MEDIA_BUS_FMT_RGB888_1X24,
+ 	 IPU6_FW_ISYS_FRAME_FORMAT_RGBA888},
++	{V4L2_META_FMT_GENERIC_8, 8, 8, MEDIA_BUS_FMT_META_8, 0},
++	{V4L2_META_FMT_GENERIC_CSI2_10, 10, 10, MEDIA_BUS_FMT_META_10, 0},
++	{V4L2_META_FMT_GENERIC_CSI2_12, 12, 12, MEDIA_BUS_FMT_META_12, 0},
++	{V4L2_META_FMT_GENERIC_CSI2_16, 16, 16, MEDIA_BUS_FMT_META_16, 0},
++	{V4L2_META_FMT_GENERIC_CSI2_24, 24, 24, MEDIA_BUS_FMT_META_24, 0},
+ };
+ 
+ static int video_open(struct file *file)
+@@ -181,12 +186,12 @@ static int ipu6_isys_vidioc_enum_framesizes(struct file *file, void *fh,
+ 	return 0;
+ }
+ 
+-static int vidioc_g_fmt_vid_cap_mplane(struct file *file, void *fh,
+-				       struct v4l2_format *fmt)
++static int vidioc_get_format(struct file *file, void *fh,
++			     struct v4l2_format *fmt)
+ {
+ 	struct ipu6_isys_video *av = video_drvdata(file);
+ 
+-	fmt->fmt.pix_mp = av->mpix;
++	*fmt = av->vfmt;
+ 
+ 	return 0;
+ }
+@@ -245,30 +250,114 @@ ipu6_isys_video_try_fmt_vid_mplane(struct ipu6_isys_video *av,
+ 	return pfmt;
+ }
+ 
+-static int vidioc_s_fmt_vid_cap_mplane(struct file *file, void *fh,
+-				       struct v4l2_format *f)
++static const struct ipu6_isys_pixelformat *
++ipu6_isys_video_try_fmt_meta(struct ipu6_isys_video *av,
++			     struct v4l2_meta_format *meta)
++{
++	const struct ipu6_isys_pixelformat *pfmt =
++		ipu6_isys_get_pixelformat(meta->dataformat);
++
++	memset(&av->vfmt, 0, sizeof(av->vfmt));
++	av->vfmt.type = V4L2_BUF_TYPE_META_CAPTURE;
++	av->pfmt = pfmt;
++
++	meta->dataformat = pfmt->pixelformat;
++	meta->width = clamp(meta->width, IPU6_ISYS_MIN_WIDTH,
++			    IPU6_ISYS_MAX_WIDTH);
++	meta->height = clamp(meta->height, IPU6_ISYS_MIN_HEIGHT,
++			     IPU6_ISYS_MAX_HEIGHT);
++
++	if (pfmt->bpp != pfmt->bpp_packed)
++		meta->bytesperline = meta->width *
++				     DIV_ROUND_UP(pfmt->bpp, BITS_PER_BYTE);
++	else
++		meta->bytesperline =
++			DIV_ROUND_UP(meta->width * pfmt->bpp, BITS_PER_BYTE);
++
++	meta->bytesperline = ALIGN(meta->bytesperline, av->isys->line_align);
++	meta->buffersize =
++		max(max(meta->buffersize, meta->bytesperline * meta->height +
++			max(meta->bytesperline,
++			    av->isys->pdata->ipdata->isys_dma_overshoot)), 1U);
++
++	return pfmt;
++}
++
++static const struct ipu6_isys_pixelformat *
++ipu6_isys_video_try_fmt(struct ipu6_isys_video *av, struct v4l2_format *f)
++{
++	if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE)
++		return ipu6_isys_video_try_fmt_vid_mplane(av, &f->fmt.pix_mp);
++	else if (f->type == V4L2_BUF_TYPE_META_CAPTURE)
++		return ipu6_isys_video_try_fmt_meta(av, &f->fmt.meta);
++	else
++		return &ipu6_isys_pfmts[0];
++}
++
++static int vidioc_set_format(struct file *file, void *fh,
++			     struct v4l2_format *f)
+ {
+ 	struct ipu6_isys_video *av = video_drvdata(file);
+ 
+ 	if (av->aq.vbq.streaming)
+ 		return -EBUSY;
+ 
+-	av->pfmt = ipu6_isys_video_try_fmt_vid_mplane(av, &f->fmt.pix_mp);
+-	av->mpix = f->fmt.pix_mp;
++	if (f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE &&
++	    f->type != V4L2_BUF_TYPE_META_CAPTURE)
++		return -EINVAL;
++
++	av->pfmt = ipu6_isys_video_try_fmt(av, f);
++	av->vfmt = *f;
+ 
+ 	return 0;
+ }
+ 
+-static int vidioc_try_fmt_vid_cap_mplane(struct file *file, void *fh,
+-					 struct v4l2_format *f)
++static int vidioc_try_format(struct file *file, void *fh,
++			     struct v4l2_format *f)
+ {
+ 	struct ipu6_isys_video *av = video_drvdata(file);
+ 
+-	ipu6_isys_video_try_fmt_vid_mplane(av, &f->fmt.pix_mp);
++	if (f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE &&
++	    f->type != V4L2_BUF_TYPE_META_CAPTURE)
++		return -EINVAL;
++
++	ipu6_isys_video_try_fmt(av, f);
+ 
+ 	return 0;
+ }
+ 
++static int vidioc_request_qbufs(struct file *file, void *priv,
++				struct v4l2_requestbuffers *p)
++{
++	struct ipu6_isys_video *av = video_drvdata(file);
++	int ret;
++
++	av->aq.vbq.is_multiplanar = V4L2_TYPE_IS_MULTIPLANAR(p->type);
++	av->aq.vbq.is_output = V4L2_TYPE_IS_OUTPUT(p->type);
++
++	ret = vb2_queue_change_type(&av->aq.vbq, p->type);
++	if (ret)
++		return ret;
++
++	return vb2_ioctl_reqbufs(file, priv, p);
++}
++
++static int vidioc_create_bufs(struct file *file, void *priv,
++			      struct v4l2_create_buffers *p)
++{
++	struct ipu6_isys_video *av = video_drvdata(file);
++	int ret;
++
++	av->aq.vbq.is_multiplanar = V4L2_TYPE_IS_MULTIPLANAR(p->format.type);
++	av->aq.vbq.is_output = V4L2_TYPE_IS_OUTPUT(p->format.type);
++
++	ret = vb2_queue_change_type(&av->aq.vbq, p->format.type);
++	if (ret)
++		return ret;
++
++	return vb2_ioctl_create_bufs(file, priv, p);
++}
++
+ static int link_validate(struct media_link *link)
+ {
+ 	struct ipu6_isys_video *av =
+@@ -279,6 +368,8 @@ static int link_validate(struct media_link *link)
+ 	struct v4l2_mbus_framefmt *s_fmt;
+ 	struct media_pad *s_pad;
+ 	u32 s_stream;
++	u32 height;
++	u32 width;
+ 	int ret = -EPIPE;
+ 
+ 	if (!link->source->entity)
+@@ -305,11 +396,13 @@ static int link_validate(struct media_link *link)
+ 		goto unlock;
+ 	}
+ 
+-	if (s_fmt->width != av->mpix.width ||
+-	    s_fmt->height != av->mpix.height || s_fmt->code != av->pfmt->code) {
++	height = ipu6_get_frame_height(&av->vfmt);
++	width = ipu6_get_frame_width(&av->vfmt);
++	if (s_fmt->width != width || s_fmt->height != height ||
++	    s_fmt->code != av->pfmt->code) {
+ 		dev_err(dev, "format mismatch %dx%d,%x != %dx%d,%x\n",
+-			s_fmt->width, s_fmt->height, s_fmt->code,
+-			av->mpix.width, av->mpix.height, av->pfmt->code);
++			s_fmt->width, s_fmt->height, s_fmt->code, width, height,
++			av->pfmt->code);
+ 		goto unlock;
+ 	}
+ 
+@@ -393,10 +486,10 @@ static int ipu6_isys_fw_pin_cfg(struct ipu6_isys_video *av,
+ 
+ 	output_pin = &cfg->output_pins[output_pins];
+ 	output_pin->input_pin_id = input_pins;
+-	output_pin->output_res.width = av->mpix.width;
+-	output_pin->output_res.height = av->mpix.height;
++	output_pin->output_res.width = ipu6_get_frame_width(&av->vfmt);
++	output_pin->output_res.height = ipu6_get_frame_height(&av->vfmt);
+ 
+-	output_pin->stride = av->mpix.plane_fmt[0].bytesperline;
++	output_pin->stride = ipu6_get_bytes_per_line(&av->vfmt);
+ 	if (av->pfmt->bpp != av->pfmt->bpp_packed)
+ 		output_pin->pt = IPU6_FW_ISYS_PIN_TYPE_RAW_SOC;
+ 	else
+@@ -663,8 +756,8 @@ void ipu6_isys_configure_stream_watermark(struct ipu6_isys_video *av,
+ 
+ 	esd = media_entity_to_v4l2_subdev(av->stream->source_entity);
+ 
+-	av->watermark.width = av->mpix.width;
+-	av->watermark.height = av->mpix.height;
++	av->watermark.width = ipu6_get_frame_width(&av->vfmt);
++	av->watermark.height = ipu6_get_frame_height(&av->vfmt);
+ 	av->watermark.sram_gran_shift = isys->pdata->ipdata->sram_gran_shift;
+ 	av->watermark.sram_gran_size = isys->pdata->ipdata->sram_gran_size;
+ 
+@@ -992,11 +1085,15 @@ static const struct v4l2_ioctl_ops ioctl_ops_mplane = {
+ 	.vidioc_querycap = ipu6_isys_vidioc_querycap,
+ 	.vidioc_enum_fmt_vid_cap = ipu6_isys_vidioc_enum_fmt,
+ 	.vidioc_enum_framesizes = ipu6_isys_vidioc_enum_framesizes,
+-	.vidioc_g_fmt_vid_cap_mplane = vidioc_g_fmt_vid_cap_mplane,
+-	.vidioc_s_fmt_vid_cap_mplane = vidioc_s_fmt_vid_cap_mplane,
+-	.vidioc_try_fmt_vid_cap_mplane = vidioc_try_fmt_vid_cap_mplane,
+-	.vidioc_reqbufs = vb2_ioctl_reqbufs,
+-	.vidioc_create_bufs = vb2_ioctl_create_bufs,
++	.vidioc_g_fmt_vid_cap_mplane = vidioc_get_format,
++	.vidioc_s_fmt_vid_cap_mplane = vidioc_set_format,
++	.vidioc_try_fmt_vid_cap_mplane = vidioc_try_format,
++	.vidioc_enum_fmt_meta_cap = ipu6_isys_vidioc_enum_fmt,
++	.vidioc_g_fmt_meta_cap = vidioc_get_format,
++	.vidioc_s_fmt_meta_cap = vidioc_set_format,
++	.vidioc_try_fmt_meta_cap = vidioc_try_format,
++	.vidioc_reqbufs = vidioc_request_qbufs,
++	.vidioc_create_bufs = vidioc_create_bufs,
+ 	.vidioc_prepare_buf = vb2_ioctl_prepare_buf,
+ 	.vidioc_querybuf = vb2_ioctl_querybuf,
+ 	.vidioc_qbuf = vb2_ioctl_qbuf,
+@@ -1199,7 +1296,8 @@ int ipu6_isys_video_init(struct ipu6_isys_video *av)
+ 
+ 	mutex_init(&av->mutex);
+ 	av->vdev.device_caps = V4L2_CAP_STREAMING | V4L2_CAP_IO_MC |
+-			       V4L2_CAP_VIDEO_CAPTURE_MPLANE;
++			       V4L2_CAP_VIDEO_CAPTURE_MPLANE |
++			       V4L2_CAP_META_CAPTURE;
+ 	av->vdev.vfl_dir = VFL_DIR_RX;
+ 
+ 	ret = ipu6_isys_queue_init(&av->aq);
+@@ -1220,8 +1318,8 @@ int ipu6_isys_video_init(struct ipu6_isys_video *av)
+ 	av->vdev.queue = &av->aq.vbq;
+ 	av->vdev.lock = &av->mutex;
+ 
+-	ipu6_isys_video_try_fmt_vid_mplane(av, &format.fmt.pix_mp);
+-	av->mpix = format.fmt.pix_mp;
++	ipu6_isys_video_try_fmt(av, &format);
++	av->vfmt = format;
+ 
+ 	set_bit(V4L2_FL_USES_V4L2_FH, &av->vdev.flags);
+ 	video_set_drvdata(&av->vdev, av);
+@@ -1251,3 +1349,52 @@ void ipu6_isys_video_cleanup(struct ipu6_isys_video *av)
+ 	media_entity_cleanup(&av->vdev.entity);
+ 	mutex_destroy(&av->mutex);
+ }
++
++u32 ipu6_get_data_size(struct v4l2_format *vfmt, int plane)
++{
++	if (vfmt->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE)
++		return vfmt->fmt.pix_mp.plane_fmt[plane].sizeimage;
++	else if (vfmt->type == V4L2_BUF_TYPE_META_CAPTURE)
++		return vfmt->fmt.meta.buffersize;
++
++	WARN_ON_ONCE(1);
++
++	return 0;
++}
++
++u32 ipu6_get_bytes_per_line(struct v4l2_format *vfmt)
++{
++	if (vfmt->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE)
++		return vfmt->fmt.pix_mp.plane_fmt[0].bytesperline;
++	else if (vfmt->type == V4L2_BUF_TYPE_META_CAPTURE)
++		return vfmt->fmt.meta.bytesperline;
++
++	WARN_ON_ONCE(1);
++
++	return 0;
++}
++
++u32 ipu6_get_frame_width(struct v4l2_format *vfmt)
++{
++	if (vfmt->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE)
++		return vfmt->fmt.pix_mp.width;
++	else if (vfmt->type == V4L2_BUF_TYPE_META_CAPTURE)
++		return vfmt->fmt.meta.width;
++
++	WARN_ON_ONCE(1);
++
++	return 0;
++}
++
++u32 ipu6_get_frame_height(struct v4l2_format *vfmt)
++{
++	if (vfmt->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE)
++		return vfmt->fmt.pix_mp.height;
++	else if (vfmt->type == V4L2_BUF_TYPE_META_CAPTURE)
++		return vfmt->fmt.meta.height;
++
++	WARN_ON_ONCE(1);
++
++	return 0;
++}
++
+diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys-video.h b/drivers/media/pci/intel/ipu6/ipu6-isys-video.h
+index 21cd33c7e277..2634ec0fd68b 100644
+--- a/drivers/media/pci/intel/ipu6/ipu6-isys-video.h
++++ b/drivers/media/pci/intel/ipu6/ipu6-isys-video.h
+@@ -90,7 +90,7 @@ struct ipu6_isys_video {
+ 	struct mutex mutex;
+ 	struct media_pad pad;
+ 	struct video_device vdev;
+-	struct v4l2_pix_format_mplane mpix;
++	struct v4l2_format vfmt;
+ 	const struct ipu6_isys_pixelformat *pfmt;
+ 	struct ipu6_isys *isys;
+ 	struct ipu6_isys_stream *stream;
+@@ -133,4 +133,9 @@ void ipu6_isys_configure_stream_watermark(struct ipu6_isys_video *av,
+ 					  bool state);
+ void ipu6_isys_update_stream_watermark(struct ipu6_isys_video *av, bool state);
+ 
++u32 ipu6_get_data_size(struct v4l2_format *vfmt, int plane);
++u32 ipu6_get_bytes_per_line(struct v4l2_format *vfmt);
++u32 ipu6_get_frame_width(struct v4l2_format *vfmt);
++u32 ipu6_get_frame_height(struct v4l2_format *vfmt);
++
+ #endif /* IPU6_ISYS_VIDEO_H */
+-- 
+2.43.2
+
+
+From 9a6fb311b81433ebbd8e0769bed19958a6a5a5f6 Mon Sep 17 00:00:00 2001
+From: Bingbu Cao <bingbu.cao@intel.com>
+Date: Thu, 11 Jan 2024 14:55:31 +0800
+Subject: [PATCH 24/33] media: ipu6/isys: support new v4l2 subdev state APIs
+
+Add support for the upcoming v4l2-subdev API changes in kernel 6.8.
+This patch is based on Sakari's branch:
+<URL:https://git.linuxtv.org/sailus/media_tree.git/log/?h=metadata>
+
+Signed-off-by: Hans de Goede <hdegoede@redhat.com>
+Signed-off-by: Bingbu Cao <bingbu.cao@intel.com>
+---
+ drivers/media/pci/intel/ipu6/ipu6-isys-csi2.c |  8 +++-----
+ .../media/pci/intel/ipu6/ipu6-isys-subdev.c   | 19 +++++++++++--------
+ .../media/pci/intel/ipu6/ipu6-isys-subdev.h   |  2 --
+ .../media/pci/intel/ipu6/ipu6-isys-video.c    |  3 +--
+ 4 files changed, 15 insertions(+), 17 deletions(-)
+
+diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys-csi2.c b/drivers/media/pci/intel/ipu6/ipu6-isys-csi2.c
+index a6430d531129..6f258cf92fc1 100644
+--- a/drivers/media/pci/intel/ipu6/ipu6-isys-csi2.c
++++ b/drivers/media/pci/intel/ipu6/ipu6-isys-csi2.c
+@@ -403,12 +403,11 @@ static int ipu6_isys_csi2_set_sel(struct v4l2_subdev *sd,
+ 	if (!sink_ffmt)
+ 		return -EINVAL;
+ 
+-	src_ffmt = v4l2_subdev_state_get_stream_format(state, sel->pad,
+-						       sel->stream);
++	src_ffmt = v4l2_subdev_state_get_format(state, sel->pad, sel->stream);
+ 	if (!src_ffmt)
+ 		return -EINVAL;
+ 
+-	crop = v4l2_subdev_state_get_stream_crop(state, sel->pad, sel->stream);
++	crop = v4l2_subdev_state_get_crop(state, sel->pad, sel->stream);
+ 	if (!crop)
+ 		return -EINVAL;
+ 
+@@ -453,7 +452,7 @@ static int ipu6_isys_csi2_get_sel(struct v4l2_subdev *sd,
+ 	if (!sink_ffmt)
+ 		return -EINVAL;
+ 
+-	crop = v4l2_subdev_state_get_stream_crop(state, sel->pad, sel->stream);
++	crop = v4l2_subdev_state_get_crop(state, sel->pad, sel->stream);
+ 	if (!crop)
+ 		return -EINVAL;
+ 
+@@ -480,7 +479,6 @@ static const struct v4l2_subdev_video_ops csi2_sd_video_ops = {
+ };
+ 
+ static const struct v4l2_subdev_pad_ops csi2_sd_pad_ops = {
+-	.init_cfg = ipu6_isys_subdev_init_cfg,
+ 	.get_fmt = v4l2_subdev_get_fmt,
+ 	.set_fmt = ipu6_isys_subdev_set_fmt,
+ 	.get_selection = ipu6_isys_csi2_get_sel,
+diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys-subdev.c b/drivers/media/pci/intel/ipu6/ipu6-isys-subdev.c
+index 3c9263ac02a3..aeccd6f93986 100644
+--- a/drivers/media/pci/intel/ipu6/ipu6-isys-subdev.c
++++ b/drivers/media/pci/intel/ipu6/ipu6-isys-subdev.c
+@@ -156,8 +156,7 @@ int ipu6_isys_subdev_set_fmt(struct v4l2_subdev *sd,
+ 	format->format.field = V4L2_FIELD_NONE;
+ 
+ 	/* Store the format and propagate it to the source pad. */
+-	fmt = v4l2_subdev_state_get_stream_format(state, format->pad,
+-						  format->stream);
++	fmt = v4l2_subdev_state_get_format(state, format->pad, format->stream);
+ 	if (!fmt)
+ 		return -EINVAL;
+ 
+@@ -182,8 +181,7 @@ int ipu6_isys_subdev_set_fmt(struct v4l2_subdev *sd,
+ 	if (ret)
+ 		return -EINVAL;
+ 
+-	crop = v4l2_subdev_state_get_stream_crop(state, other_pad,
+-						 other_stream);
++	crop = v4l2_subdev_state_get_crop(state, other_pad, other_stream);
+ 	/* reset crop */
+ 	crop->left = 0;
+ 	crop->top = 0;
+@@ -241,7 +239,7 @@ int ipu6_isys_get_stream_pad_fmt(struct v4l2_subdev *sd, u32 pad, u32 stream,
+ 		return -EINVAL;
+ 
+ 	state = v4l2_subdev_lock_and_get_active_state(sd);
+-	fmt = v4l2_subdev_state_get_stream_format(state, pad, stream);
++	fmt = v4l2_subdev_state_get_format(state, pad, stream);
+ 	if (fmt)
+ 		*format = *fmt;
+ 	v4l2_subdev_unlock_state(state);
+@@ -259,7 +257,7 @@ int ipu6_isys_get_stream_pad_crop(struct v4l2_subdev *sd, u32 pad, u32 stream,
+ 		return -EINVAL;
+ 
+ 	state = v4l2_subdev_lock_and_get_active_state(sd);
+-	rect = v4l2_subdev_state_get_stream_crop(state, pad, stream);
++	rect = v4l2_subdev_state_get_crop(state, pad, stream);
+ 	if (rect)
+ 		*crop = *rect;
+ 	v4l2_subdev_unlock_state(state);
+@@ -291,8 +289,8 @@ u32 ipu6_isys_get_src_stream_by_src_pad(struct v4l2_subdev *sd, u32 pad)
+ 	return source_stream;
+ }
+ 
+-int ipu6_isys_subdev_init_cfg(struct v4l2_subdev *sd,
+-			      struct v4l2_subdev_state *state)
++static int ipu6_isys_subdev_init_state(struct v4l2_subdev *sd,
++				       struct v4l2_subdev_state *state)
+ {
+ 	struct v4l2_subdev_route route = {
+ 		.sink_pad = 0,
+@@ -317,6 +315,10 @@ int ipu6_isys_subdev_set_routing(struct v4l2_subdev *sd,
+ 	return subdev_set_routing(sd, state, routing);
+ }
+ 
++static const struct v4l2_subdev_internal_ops ipu6_isys_subdev_internal_ops = {
++	.init_state = ipu6_isys_subdev_init_state,
++};
++
+ int ipu6_isys_subdev_init(struct ipu6_isys_subdev *asd,
+ 			  const struct v4l2_subdev_ops *ops,
+ 			  unsigned int nr_ctrls,
+@@ -334,6 +336,7 @@ int ipu6_isys_subdev_init(struct ipu6_isys_subdev *asd,
+ 			 V4L2_SUBDEV_FL_STREAMS;
+ 	asd->sd.owner = THIS_MODULE;
+ 	asd->sd.entity.function = MEDIA_ENT_F_VID_IF_BRIDGE;
++	asd->sd.internal_ops = &ipu6_isys_subdev_internal_ops;
+ 
+ 	asd->pad = devm_kcalloc(&asd->isys->adev->auxdev.dev, num_pads,
+ 				sizeof(*asd->pad), GFP_KERNEL);
+diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys-subdev.h b/drivers/media/pci/intel/ipu6/ipu6-isys-subdev.h
+index adea2a55761d..f4e32b094b5b 100644
+--- a/drivers/media/pci/intel/ipu6/ipu6-isys-subdev.h
++++ b/drivers/media/pci/intel/ipu6/ipu6-isys-subdev.h
+@@ -46,8 +46,6 @@ int ipu6_isys_get_stream_pad_fmt(struct v4l2_subdev *sd, u32 pad, u32 stream,
+ 				 struct v4l2_mbus_framefmt *format);
+ int ipu6_isys_get_stream_pad_crop(struct v4l2_subdev *sd, u32 pad, u32 stream,
+ 				  struct v4l2_rect *crop);
+-int ipu6_isys_subdev_init_cfg(struct v4l2_subdev *sd,
+-			      struct v4l2_subdev_state *state);
+ int ipu6_isys_subdev_set_routing(struct v4l2_subdev *sd,
+ 				 struct v4l2_subdev_state *state,
+ 				 enum v4l2_subdev_format_whence which,
+diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys-video.c b/drivers/media/pci/intel/ipu6/ipu6-isys-video.c
+index 1a023bf1e1a6..62d4043fc2a1 100644
+--- a/drivers/media/pci/intel/ipu6/ipu6-isys-video.c
++++ b/drivers/media/pci/intel/ipu6/ipu6-isys-video.c
+@@ -389,8 +389,7 @@ static int link_validate(struct media_link *link)
+ 
+ 	v4l2_subdev_lock_state(s_state);
+ 
+-	s_fmt = v4l2_subdev_state_get_stream_format(s_state, s_pad->index,
+-						    s_stream);
++	s_fmt = v4l2_subdev_state_get_format(s_state, s_pad->index, s_stream);
+ 	if (!s_fmt) {
+ 		dev_err(dev, "failed to get source pad format\n");
+ 		goto unlock;
+-- 
+2.43.2
+
+
+From 53ca77877d2cc7ecc39bb0ef26a1871a1c26afd1 Mon Sep 17 00:00:00 2001
+From: Hans de Goede <hdegoede@redhat.com>
+Date: Mon, 15 Jan 2024 15:57:06 +0100
+Subject: [PATCH 25/33] media: intel/ipu6: Disable packed bayer v4l2-buffer
+ formats on TGL
+
+Using CSI2 packing to store 10bpp bayer data in the v4l2-buffers does not
+work on Tiger Lake when testing with an ov01a1s sensor.
+
+Disable packed bayer formats on Tiger Lake for now.
+
+Signed-off-by: Hans de Goede <hdegoede@redhat.com>
+---
+ .../media/pci/intel/ipu6/ipu6-isys-video.c    | 65 ++++++++++++-------
+ 1 file changed, 43 insertions(+), 22 deletions(-)
+
+diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys-video.c b/drivers/media/pci/intel/ipu6/ipu6-isys-video.c
+index 62d4043fc2a1..c971ffe0b948 100644
+--- a/drivers/media/pci/intel/ipu6/ipu6-isys-video.c
++++ b/drivers/media/pci/intel/ipu6/ipu6-isys-video.c
+@@ -61,6 +61,17 @@ const struct ipu6_isys_pixelformat ipu6_isys_pfmts[] = {
+ 	 IPU6_FW_ISYS_FRAME_FORMAT_RAW8},
+ 	{V4L2_PIX_FMT_SRGGB8, 8, 8, MEDIA_BUS_FMT_SRGGB8_1X8,
+ 	 IPU6_FW_ISYS_FRAME_FORMAT_RAW8},
++	{V4L2_PIX_FMT_UYVY, 16, 16, MEDIA_BUS_FMT_UYVY8_1X16,
++	 IPU6_FW_ISYS_FRAME_FORMAT_UYVY},
++	{V4L2_PIX_FMT_YUYV, 16, 16, MEDIA_BUS_FMT_YUYV8_1X16,
++	 IPU6_FW_ISYS_FRAME_FORMAT_YUYV},
++	{V4L2_PIX_FMT_RGB565, 16, 16, MEDIA_BUS_FMT_RGB565_1X16,
++	 IPU6_FW_ISYS_FRAME_FORMAT_RGB565},
++	{V4L2_PIX_FMT_BGR24, 24, 24, MEDIA_BUS_FMT_RGB888_1X24,
++	 IPU6_FW_ISYS_FRAME_FORMAT_RGBA888},
++};
++
++const struct ipu6_isys_pixelformat ipu6_isys_pfmts_packed[] = {
+ 	{V4L2_PIX_FMT_SBGGR12P, 12, 12, MEDIA_BUS_FMT_SBGGR12_1X12,
+ 	 IPU6_FW_ISYS_FRAME_FORMAT_RAW12},
+ 	{V4L2_PIX_FMT_SGBRG12P, 12, 12, MEDIA_BUS_FMT_SGBRG12_1X12,
+@@ -77,19 +88,6 @@ const struct ipu6_isys_pixelformat ipu6_isys_pfmts[] = {
+ 	 IPU6_FW_ISYS_FRAME_FORMAT_RAW10},
+ 	{V4L2_PIX_FMT_SRGGB10P, 10, 10, MEDIA_BUS_FMT_SRGGB10_1X10,
+ 	 IPU6_FW_ISYS_FRAME_FORMAT_RAW10},
+-	{V4L2_PIX_FMT_UYVY, 16, 16, MEDIA_BUS_FMT_UYVY8_1X16,
+-	 IPU6_FW_ISYS_FRAME_FORMAT_UYVY},
+-	{V4L2_PIX_FMT_YUYV, 16, 16, MEDIA_BUS_FMT_YUYV8_1X16,
+-	 IPU6_FW_ISYS_FRAME_FORMAT_YUYV},
+-	{V4L2_PIX_FMT_RGB565, 16, 16, MEDIA_BUS_FMT_RGB565_1X16,
+-	 IPU6_FW_ISYS_FRAME_FORMAT_RGB565},
+-	{V4L2_PIX_FMT_BGR24, 24, 24, MEDIA_BUS_FMT_RGB888_1X24,
+-	 IPU6_FW_ISYS_FRAME_FORMAT_RGBA888},
+-	{V4L2_META_FMT_GENERIC_8, 8, 8, MEDIA_BUS_FMT_META_8, 0},
+-	{V4L2_META_FMT_GENERIC_CSI2_10, 10, 10, MEDIA_BUS_FMT_META_10, 0},
+-	{V4L2_META_FMT_GENERIC_CSI2_12, 12, 12, MEDIA_BUS_FMT_META_12, 0},
+-	{V4L2_META_FMT_GENERIC_CSI2_16, 16, 16, MEDIA_BUS_FMT_META_16, 0},
+-	{V4L2_META_FMT_GENERIC_CSI2_24, 24, 24, MEDIA_BUS_FMT_META_24, 0},
+ };
+ 
+ static int video_open(struct file *file)
+@@ -114,14 +112,27 @@ static int video_release(struct file *file)
+ 	return vb2_fop_release(file);
+ }
+ 
++static const struct ipu6_isys_pixelformat *
++ipu6_isys_get_pixelformat_by_idx(unsigned int idx)
++{
++	if (idx < ARRAY_SIZE(ipu6_isys_pfmts))
++		return &ipu6_isys_pfmts[idx];
++
++	idx -= ARRAY_SIZE(ipu6_isys_pfmts);
++
++	if (idx < ARRAY_SIZE(ipu6_isys_pfmts_packed))
++		return &ipu6_isys_pfmts_packed[idx];
++
++	return NULL;
++}
++
+ static const struct ipu6_isys_pixelformat *
+ ipu6_isys_get_pixelformat(u32 pixelformat)
+ {
++	const struct ipu6_isys_pixelformat *pfmt;
+ 	unsigned int i;
+ 
+-	for (i = 0; i < ARRAY_SIZE(ipu6_isys_pfmts); i++) {
+-		const struct ipu6_isys_pixelformat *pfmt = &ipu6_isys_pfmts[i];
+-
++	for (i = 0; (pfmt = ipu6_isys_get_pixelformat_by_idx(i)); i++) {
+ 		if (pfmt->pixelformat == pixelformat)
+ 			return pfmt;
+ 	}
+@@ -143,24 +154,34 @@ int ipu6_isys_vidioc_querycap(struct file *file, void *fh,
+ int ipu6_isys_vidioc_enum_fmt(struct file *file, void *fh,
+ 			      struct v4l2_fmtdesc *f)
+ {
+-	unsigned int i, found = 0;
++	struct ipu6_isys_video *av = video_drvdata(file);
++	const struct ipu6_isys_pixelformat *fmt;
++	unsigned int i, nfmts, found = 0;
++
++	nfmts = ARRAY_SIZE(ipu6_isys_pfmts);
++	/* Disable packed formats on TGL for now, TGL has 8 CSI ports */
++	if (av->isys->pdata->ipdata->csi2.nports != 8)
++		nfmts += ARRAY_SIZE(ipu6_isys_pfmts_packed);
+ 
+-	if (f->index >= ARRAY_SIZE(ipu6_isys_pfmts))
++	if (f->index >= nfmts)
+ 		return -EINVAL;
+ 
+ 	if (!f->mbus_code) {
++		fmt = ipu6_isys_get_pixelformat_by_idx(f->index);
+ 		f->flags = 0;
+-		f->pixelformat = ipu6_isys_pfmts[f->index].pixelformat;
++		f->pixelformat = fmt->pixelformat;
+ 		return 0;
+ 	}
+ 
+-	for (i = 0; i < ARRAY_SIZE(ipu6_isys_pfmts); i++) {
+-		if (f->mbus_code != ipu6_isys_pfmts[i].code)
++	for (i = 0; i < nfmts; i++) {
++		fmt = ipu6_isys_get_pixelformat_by_idx(i);
++
++		if (f->mbus_code != fmt->code)
+ 			continue;
+ 
+ 		if (f->index == found) {
+ 			f->flags = 0;
+-			f->pixelformat = ipu6_isys_pfmts[i].pixelformat;
++			f->pixelformat = fmt->pixelformat;
+ 			return 0;
+ 		}
+ 		found++;
+-- 
+2.43.2
+
+
+From ed407043f03e9af2b09ab8ad449c2716ce7fde01 Mon Sep 17 00:00:00 2001
+From: Hans de Goede <hdegoede@redhat.com>
+Date: Mon, 6 Nov 2023 12:13:42 +0100
+Subject: [PATCH 26/33] media: Add ov01a1s driver
+
+Add ov01a1s driver from:
+https://github.com/intel/ipu6-drivers/
+
+Signed-off-by: Hans de Goede <hdegoede@redhat.com>
+---
+ drivers/media/i2c/Kconfig   |    9 +
+ drivers/media/i2c/Makefile  |    1 +
+ drivers/media/i2c/ov01a1s.c | 1191 +++++++++++++++++++++++++++++++++++
+ 3 files changed, 1201 insertions(+)
+ create mode 100644 drivers/media/i2c/ov01a1s.c
+
+diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig
+index 4c3435921f19..08f934740980 100644
+--- a/drivers/media/i2c/Kconfig
++++ b/drivers/media/i2c/Kconfig
+@@ -313,6 +313,15 @@ config VIDEO_OV01A10
+ 	  To compile this driver as a module, choose M here: the
+ 	  module will be called ov01a10.
+ 
++config VIDEO_OV01A1S
++	tristate "OmniVision OV01A1S sensor support"
++	help
++	  This is a Video4Linux2 sensor driver for the OmniVision
++	  OV01A1S camera.
++
++	  To compile this driver as a module, choose M here: the
++	  module will be called ov01a1s.
++
+ config VIDEO_OV02A10
+ 	tristate "OmniVision OV02A10 sensor support"
+ 	help
+diff --git a/drivers/media/i2c/Makefile b/drivers/media/i2c/Makefile
+index dfbe6448b549..53ea54c2cf01 100644
+--- a/drivers/media/i2c/Makefile
++++ b/drivers/media/i2c/Makefile
+@@ -76,6 +76,7 @@ obj-$(CONFIG_VIDEO_MT9V032) += mt9v032.o
+ obj-$(CONFIG_VIDEO_MT9V111) += mt9v111.o
+ obj-$(CONFIG_VIDEO_OG01A1B) += og01a1b.o
+ obj-$(CONFIG_VIDEO_OV01A10) += ov01a10.o
++obj-$(CONFIG_VIDEO_OV01A1S) += ov01a1s.o
+ obj-$(CONFIG_VIDEO_OV02A10) += ov02a10.o
+ obj-$(CONFIG_VIDEO_OV08D10) += ov08d10.o
+ obj-$(CONFIG_VIDEO_OV08X40) += ov08x40.o
+diff --git a/drivers/media/i2c/ov01a1s.c b/drivers/media/i2c/ov01a1s.c
+new file mode 100644
+index 000000000000..0dcce8b492b4
+--- /dev/null
++++ b/drivers/media/i2c/ov01a1s.c
+@@ -0,0 +1,1191 @@
++// SPDX-License-Identifier: GPL-2.0
++// Copyright (c) 2020-2022 Intel Corporation.
++
++#include <asm/unaligned.h>
++#include <linux/acpi.h>
++#include <linux/delay.h>
++#include <linux/i2c.h>
++#include <linux/module.h>
++#include <linux/pm_runtime.h>
++#include <linux/version.h>
++#include <media/v4l2-ctrls.h>
++#include <media/v4l2-device.h>
++#include <media/v4l2-fwnode.h>
++#if IS_ENABLED(CONFIG_INTEL_SKL_INT3472)
++#include <linux/clk.h>
++#include <linux/gpio/consumer.h>
++#elif IS_ENABLED(CONFIG_POWER_CTRL_LOGIC)
++#include "power_ctrl_logic.h"
++#endif
++#if IS_ENABLED(CONFIG_INTEL_VSC)
++#include <linux/vsc.h>
++#endif
++
++#define OV01A1S_LINK_FREQ_400MHZ	400000000ULL
++#define OV01A1S_SCLK			40000000LL
++#define OV01A1S_MCLK			19200000
++#define OV01A1S_DATA_LANES		1
++#define OV01A1S_RGB_DEPTH		10
++
++#define OV01A1S_REG_CHIP_ID		0x300a
++#define OV01A1S_CHIP_ID			0x560141
++
++#define OV01A1S_REG_MODE_SELECT		0x0100
++#define OV01A1S_MODE_STANDBY		0x00
++#define OV01A1S_MODE_STREAMING		0x01
++
++/* vertical-timings from sensor */
++#define OV01A1S_REG_VTS			0x380e
++#define OV01A1S_VTS_DEF			0x0380
++#define OV01A1S_VTS_MIN			0x0380
++#define OV01A1S_VTS_MAX			0xffff
++
++/* Exposure controls from sensor */
++#define OV01A1S_REG_EXPOSURE		0x3501
++#define OV01A1S_EXPOSURE_MIN		4
++#define OV01A1S_EXPOSURE_MAX_MARGIN	8
++#define OV01A1S_EXPOSURE_STEP		1
++
++/* Analog gain controls from sensor */
++#define OV01A1S_REG_ANALOG_GAIN		0x3508
++#define OV01A1S_ANAL_GAIN_MIN		0x100
++#define OV01A1S_ANAL_GAIN_MAX		0xffff
++#define OV01A1S_ANAL_GAIN_STEP		1
++
++/* Digital gain controls from sensor */
++#define OV01A1S_REG_DIGILAL_GAIN_B	0x350A
++#define OV01A1S_REG_DIGITAL_GAIN_GB	0x3510
++#define OV01A1S_REG_DIGITAL_GAIN_GR	0x3513
++#define OV01A1S_REG_DIGITAL_GAIN_R	0x3516
++#define OV01A1S_DGTL_GAIN_MIN		0
++#define OV01A1S_DGTL_GAIN_MAX		0x3ffff
++#define OV01A1S_DGTL_GAIN_STEP		1
++#define OV01A1S_DGTL_GAIN_DEFAULT	1024
++
++/* Test Pattern Control */
++#define OV01A1S_REG_TEST_PATTERN		0x4503
++#define OV01A1S_TEST_PATTERN_ENABLE	BIT(7)
++#define OV01A1S_TEST_PATTERN_BAR_SHIFT	0
++
++enum {
++	OV01A1S_LINK_FREQ_400MHZ_INDEX,
++};
++
++struct ov01a1s_reg {
++	u16 address;
++	u8 val;
++};
++
++struct ov01a1s_reg_list {
++	u32 num_of_regs;
++	const struct ov01a1s_reg *regs;
++};
++
++struct ov01a1s_link_freq_config {
++	const struct ov01a1s_reg_list reg_list;
++};
++
++struct ov01a1s_mode {
++	/* Frame width in pixels */
++	u32 width;
++
++	/* Frame height in pixels */
++	u32 height;
++
++	/* Horizontal timining size */
++	u32 hts;
++
++	/* Default vertical timining size */
++	u32 vts_def;
++
++	/* Min vertical timining size */
++	u32 vts_min;
++
++	/* Link frequency needed for this resolution */
++	u32 link_freq_index;
++
++	/* Sensor register settings for this resolution */
++	const struct ov01a1s_reg_list reg_list;
++};
++
++static const struct ov01a1s_reg mipi_data_rate_720mbps[] = {
++};
++
++static const struct ov01a1s_reg sensor_1296x800_setting[] = {
++	{0x0103, 0x01},
++	{0x0302, 0x00},
++	{0x0303, 0x06},
++	{0x0304, 0x01},
++	{0x0305, 0x90},
++	{0x0306, 0x00},
++	{0x0308, 0x01},
++	{0x0309, 0x00},
++	{0x030c, 0x01},
++	{0x0322, 0x01},
++	{0x0323, 0x06},
++	{0x0324, 0x01},
++	{0x0325, 0x68},
++	{0x3002, 0xa1},
++	{0x301e, 0xf0},
++	{0x3022, 0x01},
++	{0x3501, 0x03},
++	{0x3502, 0x78},
++	{0x3504, 0x0c},
++	{0x3508, 0x01},
++	{0x3509, 0x00},
++	{0x3601, 0xc0},
++	{0x3603, 0x71},
++	{0x3610, 0x68},
++	{0x3611, 0x86},
++	{0x3640, 0x10},
++	{0x3641, 0x80},
++	{0x3642, 0xdc},
++	{0x3646, 0x55},
++	{0x3647, 0x57},
++	{0x364b, 0x00},
++	{0x3653, 0x10},
++	{0x3655, 0x00},
++	{0x3656, 0x00},
++	{0x365f, 0x0f},
++	{0x3661, 0x45},
++	{0x3662, 0x24},
++	{0x3663, 0x11},
++	{0x3664, 0x07},
++	{0x3709, 0x34},
++	{0x370b, 0x6f},
++	{0x3714, 0x22},
++	{0x371b, 0x27},
++	{0x371c, 0x67},
++	{0x371d, 0xa7},
++	{0x371e, 0xe7},
++	{0x3730, 0x81},
++	{0x3733, 0x10},
++	{0x3734, 0x40},
++	{0x3737, 0x04},
++	{0x3739, 0x1c},
++	{0x3767, 0x00},
++	{0x376c, 0x81},
++	{0x3772, 0x14},
++	{0x37c2, 0x04},
++	{0x37d8, 0x03},
++	{0x37d9, 0x0c},
++	{0x37e0, 0x00},
++	{0x37e1, 0x08},
++	{0x37e2, 0x10},
++	{0x37e3, 0x04},
++	{0x37e4, 0x04},
++	{0x37e5, 0x03},
++	{0x37e6, 0x04},
++	{0x3800, 0x00},
++	{0x3801, 0x00},
++	{0x3802, 0x00},
++	{0x3803, 0x00},
++	{0x3804, 0x05},
++	{0x3805, 0x0f},
++	{0x3806, 0x03},
++	{0x3807, 0x2f},
++	{0x3808, 0x05},
++	{0x3809, 0x00},
++	{0x380a, 0x03},
++	{0x380b, 0x1e},
++	{0x380c, 0x05},
++	{0x380d, 0xd0},
++	{0x380e, 0x03},
++	{0x380f, 0x80},
++	{0x3810, 0x00},
++	{0x3811, 0x09},
++	{0x3812, 0x00},
++	{0x3813, 0x08},
++	{0x3814, 0x01},
++	{0x3815, 0x01},
++	{0x3816, 0x01},
++	{0x3817, 0x01},
++	{0x3820, 0xa8},
++	{0x3822, 0x03},
++	{0x3832, 0x28},
++	{0x3833, 0x10},
++	{0x3b00, 0x00},
++	{0x3c80, 0x00},
++	{0x3c88, 0x02},
++	{0x3c8c, 0x07},
++	{0x3c8d, 0x40},
++	{0x3cc7, 0x80},
++	{0x4000, 0xc3},
++	{0x4001, 0xe0},
++	{0x4003, 0x40},
++	{0x4008, 0x02},
++	{0x4009, 0x19},
++	{0x400a, 0x01},
++	{0x400b, 0x6c},
++	{0x4011, 0x00},
++	{0x4041, 0x00},
++	{0x4300, 0xff},
++	{0x4301, 0x00},
++	{0x4302, 0x0f},
++	{0x4503, 0x00},
++	{0x4601, 0x50},
++	{0x481f, 0x34},
++	{0x4825, 0x33},
++	{0x4837, 0x14},
++	{0x4881, 0x40},
++	{0x4883, 0x01},
++	{0x4890, 0x00},
++	{0x4901, 0x00},
++	{0x4902, 0x00},
++	{0x4b00, 0x2a},
++	{0x4b0d, 0x00},
++	{0x450a, 0x04},
++	{0x450b, 0x00},
++	{0x5000, 0x65},
++	{0x5004, 0x00},
++	{0x5080, 0x40},
++	{0x5200, 0x18},
++	{0x4837, 0x14},
++	{0x0305, 0xf4},
++	{0x0325, 0xc2},
++	{0x3808, 0x05},
++	{0x3809, 0x10},
++	{0x380a, 0x03},
++	{0x380b, 0x1e},
++	{0x3810, 0x00},
++	{0x3811, 0x00},
++	{0x3812, 0x00},
++	{0x3813, 0x09},
++	{0x3820, 0x88},
++	{0x373d, 0x24},
++};
++
++static const char * const ov01a1s_test_pattern_menu[] = {
++	"Disabled",
++	"Color Bar",
++	"Top-Bottom Darker Color Bar",
++	"Right-Left Darker Color Bar",
++	"Color Bar type 4",
++};
++
++static const s64 link_freq_menu_items[] = {
++	OV01A1S_LINK_FREQ_400MHZ,
++};
++
++static const struct ov01a1s_link_freq_config link_freq_configs[] = {
++	[OV01A1S_LINK_FREQ_400MHZ_INDEX] = {
++		.reg_list = {
++			.num_of_regs = ARRAY_SIZE(mipi_data_rate_720mbps),
++			.regs = mipi_data_rate_720mbps,
++		}
++	},
++};
++
++static const struct ov01a1s_mode supported_modes[] = {
++	{
++		.width = 1296,
++		.height = 798,
++		.hts = 1488,
++		.vts_def = OV01A1S_VTS_DEF,
++		.vts_min = OV01A1S_VTS_MIN,
++		.reg_list = {
++			.num_of_regs = ARRAY_SIZE(sensor_1296x800_setting),
++			.regs = sensor_1296x800_setting,
++		},
++		.link_freq_index = OV01A1S_LINK_FREQ_400MHZ_INDEX,
++	},
++};
++
++struct ov01a1s {
++	struct v4l2_subdev sd;
++	struct media_pad pad;
++	struct v4l2_ctrl_handler ctrl_handler;
++
++	/* V4L2 Controls */
++	struct v4l2_ctrl *link_freq;
++	struct v4l2_ctrl *pixel_rate;
++	struct v4l2_ctrl *vblank;
++	struct v4l2_ctrl *hblank;
++	struct v4l2_ctrl *exposure;
++#if IS_ENABLED(CONFIG_INTEL_VSC)
++	struct v4l2_ctrl *privacy_status;
++
++	/* VSC settings */
++	struct vsc_mipi_config conf;
++	struct vsc_camera_status status;
++#endif
++
++	/* Current mode */
++	const struct ov01a1s_mode *cur_mode;
++
++	/* To serialize asynchronus callbacks */
++	struct mutex mutex;
++
++	/* i2c client */
++	struct i2c_client *client;
++
++#if IS_ENABLED(CONFIG_INTEL_SKL_INT3472)
++	/* GPIO for reset */
++	struct gpio_desc *reset_gpio;
++	/* GPIO for powerdown */
++	struct gpio_desc *powerdown_gpio;
++	/* Power enable */
++	struct regulator *avdd;
++	/* Clock provider */
++	struct clk *clk;
++#endif
++
++	enum {
++		OV01A1S_USE_DEFAULT = 0,
++#if IS_ENABLED(CONFIG_INTEL_SKL_INT3472) || IS_ENABLED(CONFIG_POWER_CTRL_LOGIC)
++		OV01A1S_USE_INT3472 = 1,
++#endif
++#if IS_ENABLED(CONFIG_INTEL_VSC)
++		OV01A1S_USE_INTEL_VSC = 2,
++#endif
++	} power_type;
++
++	/* Streaming on/off */
++	bool streaming;
++};
++
++static inline struct ov01a1s *to_ov01a1s(struct v4l2_subdev *subdev)
++{
++	return container_of(subdev, struct ov01a1s, sd);
++}
++
++static int ov01a1s_read_reg(struct ov01a1s *ov01a1s, u16 reg, u16 len, u32 *val)
++{
++	struct i2c_client *client = ov01a1s->client;
++	struct i2c_msg msgs[2];
++	u8 addr_buf[2];
++	u8 data_buf[4] = {0};
++	int ret = 0;
++
++	if (len > sizeof(data_buf))
++		return -EINVAL;
++
++	put_unaligned_be16(reg, addr_buf);
++	msgs[0].addr = client->addr;
++	msgs[0].flags = 0;
++	msgs[0].len = sizeof(addr_buf);
++	msgs[0].buf = addr_buf;
++	msgs[1].addr = client->addr;
++	msgs[1].flags = I2C_M_RD;
++	msgs[1].len = len;
++	msgs[1].buf = &data_buf[sizeof(data_buf) - len];
++
++	ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs));
++	if (ret != ARRAY_SIZE(msgs))
++		return ret < 0 ? ret : -EIO;
++
++	*val = get_unaligned_be32(data_buf);
++
++	return 0;
++}
++
++static int ov01a1s_write_reg(struct ov01a1s *ov01a1s, u16 reg, u16 len, u32 val)
++{
++	struct i2c_client *client = ov01a1s->client;
++	u8 buf[6];
++	int ret = 0;
++
++	if (len > 4)
++		return -EINVAL;
++
++	put_unaligned_be16(reg, buf);
++	put_unaligned_be32(val << 8 * (4 - len), buf + 2);
++
++	ret = i2c_master_send(client, buf, len + 2);
++	if (ret != len + 2)
++		return ret < 0 ? ret : -EIO;
++
++	return 0;
++}
++
++static int ov01a1s_write_reg_list(struct ov01a1s *ov01a1s,
++				  const struct ov01a1s_reg_list *r_list)
++{
++	struct i2c_client *client = ov01a1s->client;
++	unsigned int i;
++	int ret = 0;
++
++	for (i = 0; i < r_list->num_of_regs; i++) {
++		ret = ov01a1s_write_reg(ov01a1s, r_list->regs[i].address, 1,
++					r_list->regs[i].val);
++		if (ret) {
++			dev_err_ratelimited(&client->dev,
++					    "write reg 0x%4.4x return err = %d",
++					    r_list->regs[i].address, ret);
++			return ret;
++		}
++	}
++
++	return 0;
++}
++
++static int ov01a1s_update_digital_gain(struct ov01a1s *ov01a1s, u32 d_gain)
++{
++	struct i2c_client *client = ov01a1s->client;
++	u32 real = d_gain << 6;
++	int ret = 0;
++
++	ret = ov01a1s_write_reg(ov01a1s, OV01A1S_REG_DIGILAL_GAIN_B, 3, real);
++	if (ret) {
++		dev_err(&client->dev, "failed to set OV01A1S_REG_DIGITAL_GAIN_B");
++		return ret;
++	}
++	ret = ov01a1s_write_reg(ov01a1s, OV01A1S_REG_DIGITAL_GAIN_GB, 3, real);
++	if (ret) {
++		dev_err(&client->dev, "failed to set OV01A1S_REG_DIGITAL_GAIN_GB");
++		return ret;
++	}
++	ret = ov01a1s_write_reg(ov01a1s, OV01A1S_REG_DIGITAL_GAIN_GR, 3, real);
++	if (ret) {
++		dev_err(&client->dev, "failed to set OV01A1S_REG_DIGITAL_GAIN_GR");
++		return ret;
++	}
++
++	ret = ov01a1s_write_reg(ov01a1s, OV01A1S_REG_DIGITAL_GAIN_R, 3, real);
++	if (ret) {
++		dev_err(&client->dev, "failed to set OV01A1S_REG_DIGITAL_GAIN_R");
++		return ret;
++	}
++	return ret;
++}
++
++static int ov01a1s_test_pattern(struct ov01a1s *ov01a1s, u32 pattern)
++{
++	if (pattern)
++		pattern = (pattern - 1) << OV01A1S_TEST_PATTERN_BAR_SHIFT |
++			  OV01A1S_TEST_PATTERN_ENABLE;
++
++	return ov01a1s_write_reg(ov01a1s, OV01A1S_REG_TEST_PATTERN, 1, pattern);
++}
++
++static int ov01a1s_set_ctrl(struct v4l2_ctrl *ctrl)
++{
++	struct ov01a1s *ov01a1s = container_of(ctrl->handler,
++					     struct ov01a1s, ctrl_handler);
++	struct i2c_client *client = ov01a1s->client;
++	s64 exposure_max;
++	int ret = 0;
++
++	/* Propagate change of current control to all related controls */
++	if (ctrl->id == V4L2_CID_VBLANK) {
++		/* Update max exposure while meeting expected vblanking */
++		exposure_max = ov01a1s->cur_mode->height + ctrl->val -
++			       OV01A1S_EXPOSURE_MAX_MARGIN;
++		__v4l2_ctrl_modify_range(ov01a1s->exposure,
++					 ov01a1s->exposure->minimum,
++					 exposure_max, ov01a1s->exposure->step,
++					 exposure_max);
++	}
++
++	/* V4L2 controls values will be applied only when power is already up */
++	if (!pm_runtime_get_if_in_use(&client->dev))
++		return 0;
++
++	switch (ctrl->id) {
++	case V4L2_CID_ANALOGUE_GAIN:
++		ret = ov01a1s_write_reg(ov01a1s, OV01A1S_REG_ANALOG_GAIN, 2,
++					ctrl->val);
++		break;
++
++	case V4L2_CID_DIGITAL_GAIN:
++		ret = ov01a1s_update_digital_gain(ov01a1s, ctrl->val);
++		break;
++
++	case V4L2_CID_EXPOSURE:
++		ret = ov01a1s_write_reg(ov01a1s, OV01A1S_REG_EXPOSURE, 2,
++					ctrl->val);
++		break;
++
++	case V4L2_CID_VBLANK:
++		ret = ov01a1s_write_reg(ov01a1s, OV01A1S_REG_VTS, 2,
++					ov01a1s->cur_mode->height + ctrl->val);
++		break;
++
++	case V4L2_CID_TEST_PATTERN:
++		ret = ov01a1s_test_pattern(ov01a1s, ctrl->val);
++		break;
++
++#if IS_ENABLED(CONFIG_INTEL_VSC)
++	case V4L2_CID_PRIVACY:
++		dev_dbg(&client->dev, "set privacy to %d", ctrl->val);
++		break;
++
++#endif
++	default:
++		ret = -EINVAL;
++		break;
++	}
++
++	pm_runtime_put(&client->dev);
++
++	return ret;
++}
++
++static const struct v4l2_ctrl_ops ov01a1s_ctrl_ops = {
++	.s_ctrl = ov01a1s_set_ctrl,
++};
++
++static int ov01a1s_init_controls(struct ov01a1s *ov01a1s)
++{
++	struct v4l2_ctrl_handler *ctrl_hdlr;
++	const struct ov01a1s_mode *cur_mode;
++	s64 exposure_max, h_blank;
++	u32 vblank_min, vblank_max, vblank_default;
++	int size;
++	int ret = 0;
++
++	ctrl_hdlr = &ov01a1s->ctrl_handler;
++#if IS_ENABLED(CONFIG_INTEL_VSC)
++	ret = v4l2_ctrl_handler_init(ctrl_hdlr, 9);
++#else
++	ret = v4l2_ctrl_handler_init(ctrl_hdlr, 8);
++#endif
++	if (ret)
++		return ret;
++
++	ctrl_hdlr->lock = &ov01a1s->mutex;
++	cur_mode = ov01a1s->cur_mode;
++	size = ARRAY_SIZE(link_freq_menu_items);
++
++	ov01a1s->link_freq = v4l2_ctrl_new_int_menu(ctrl_hdlr,
++						    &ov01a1s_ctrl_ops,
++						    V4L2_CID_LINK_FREQ,
++						    size - 1, 0,
++						    link_freq_menu_items);
++	if (ov01a1s->link_freq)
++		ov01a1s->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY;
++
++	ov01a1s->pixel_rate = v4l2_ctrl_new_std(ctrl_hdlr, &ov01a1s_ctrl_ops,
++						V4L2_CID_PIXEL_RATE, 0,
++						OV01A1S_SCLK, 1, OV01A1S_SCLK);
++
++	vblank_min = cur_mode->vts_min - cur_mode->height;
++	vblank_max = OV01A1S_VTS_MAX - cur_mode->height;
++	vblank_default = cur_mode->vts_def - cur_mode->height;
++	ov01a1s->vblank = v4l2_ctrl_new_std(ctrl_hdlr, &ov01a1s_ctrl_ops,
++					    V4L2_CID_VBLANK, vblank_min,
++					    vblank_max, 1, vblank_default);
++
++	h_blank = cur_mode->hts - cur_mode->width;
++	ov01a1s->hblank = v4l2_ctrl_new_std(ctrl_hdlr, &ov01a1s_ctrl_ops,
++					    V4L2_CID_HBLANK, h_blank, h_blank,
++					    1, h_blank);
++	if (ov01a1s->hblank)
++		ov01a1s->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY;
++#if IS_ENABLED(CONFIG_INTEL_VSC)
++	ov01a1s->privacy_status = v4l2_ctrl_new_std(ctrl_hdlr,
++						    &ov01a1s_ctrl_ops,
++						    V4L2_CID_PRIVACY,
++						    0, 1, 1, 0);
++#endif
++
++	v4l2_ctrl_new_std(ctrl_hdlr, &ov01a1s_ctrl_ops, V4L2_CID_ANALOGUE_GAIN,
++			  OV01A1S_ANAL_GAIN_MIN, OV01A1S_ANAL_GAIN_MAX,
++			  OV01A1S_ANAL_GAIN_STEP, OV01A1S_ANAL_GAIN_MIN);
++	v4l2_ctrl_new_std(ctrl_hdlr, &ov01a1s_ctrl_ops, V4L2_CID_DIGITAL_GAIN,
++			  OV01A1S_DGTL_GAIN_MIN, OV01A1S_DGTL_GAIN_MAX,
++			  OV01A1S_DGTL_GAIN_STEP, OV01A1S_DGTL_GAIN_DEFAULT);
++	exposure_max = cur_mode->vts_def - OV01A1S_EXPOSURE_MAX_MARGIN;
++	ov01a1s->exposure = v4l2_ctrl_new_std(ctrl_hdlr, &ov01a1s_ctrl_ops,
++					      V4L2_CID_EXPOSURE,
++					      OV01A1S_EXPOSURE_MIN,
++					      exposure_max,
++					      OV01A1S_EXPOSURE_STEP,
++					      exposure_max);
++	v4l2_ctrl_new_std_menu_items(ctrl_hdlr, &ov01a1s_ctrl_ops,
++				     V4L2_CID_TEST_PATTERN,
++				     ARRAY_SIZE(ov01a1s_test_pattern_menu) - 1,
++				     0, 0, ov01a1s_test_pattern_menu);
++	if (ctrl_hdlr->error)
++		return ctrl_hdlr->error;
++
++	ov01a1s->sd.ctrl_handler = ctrl_hdlr;
++
++	return 0;
++}
++
++static void ov01a1s_update_pad_format(const struct ov01a1s_mode *mode,
++				      struct v4l2_mbus_framefmt *fmt)
++{
++	fmt->width = mode->width;
++	fmt->height = mode->height;
++	fmt->code = MEDIA_BUS_FMT_SGRBG10_1X10;
++	fmt->field = V4L2_FIELD_NONE;
++}
++
++#if IS_ENABLED(CONFIG_INTEL_VSC)
++static void ov01a1s_vsc_privacy_callback(void *handle,
++				       enum vsc_privacy_status status)
++{
++	struct ov01a1s *ov01a1s = handle;
++
++	v4l2_ctrl_s_ctrl(ov01a1s->privacy_status, !status);
++}
++
++#endif
++static int ov01a1s_start_streaming(struct ov01a1s *ov01a1s)
++{
++	struct i2c_client *client = ov01a1s->client;
++	const struct ov01a1s_reg_list *reg_list;
++	int link_freq_index;
++	int ret = 0;
++
++	link_freq_index = ov01a1s->cur_mode->link_freq_index;
++	reg_list = &link_freq_configs[link_freq_index].reg_list;
++	ret = ov01a1s_write_reg_list(ov01a1s, reg_list);
++	if (ret) {
++		dev_err(&client->dev, "failed to set plls");
++		return ret;
++	}
++
++	reg_list = &ov01a1s->cur_mode->reg_list;
++	ret = ov01a1s_write_reg_list(ov01a1s, reg_list);
++	if (ret) {
++		dev_err(&client->dev, "failed to set mode");
++		return ret;
++	}
++
++	ret = __v4l2_ctrl_handler_setup(ov01a1s->sd.ctrl_handler);
++	if (ret)
++		return ret;
++
++	ret = ov01a1s_write_reg(ov01a1s, OV01A1S_REG_MODE_SELECT, 1,
++				OV01A1S_MODE_STREAMING);
++	if (ret)
++		dev_err(&client->dev, "failed to start streaming");
++
++	return ret;
++}
++
++static void ov01a1s_stop_streaming(struct ov01a1s *ov01a1s)
++{
++	struct i2c_client *client = ov01a1s->client;
++	int ret = 0;
++
++	ret = ov01a1s_write_reg(ov01a1s, OV01A1S_REG_MODE_SELECT, 1,
++				OV01A1S_MODE_STANDBY);
++	if (ret)
++		dev_err(&client->dev, "failed to stop streaming");
++}
++
++static int ov01a1s_set_stream(struct v4l2_subdev *sd, int enable)
++{
++	struct ov01a1s *ov01a1s = to_ov01a1s(sd);
++	struct i2c_client *client = ov01a1s->client;
++	int ret = 0;
++
++	if (ov01a1s->streaming == enable)
++		return 0;
++
++	mutex_lock(&ov01a1s->mutex);
++	if (enable) {
++		ret = pm_runtime_get_sync(&client->dev);
++		if (ret < 0) {
++			pm_runtime_put_noidle(&client->dev);
++			mutex_unlock(&ov01a1s->mutex);
++			return ret;
++		}
++
++		ret = ov01a1s_start_streaming(ov01a1s);
++		if (ret) {
++			enable = 0;
++			ov01a1s_stop_streaming(ov01a1s);
++			pm_runtime_put(&client->dev);
++		}
++	} else {
++		ov01a1s_stop_streaming(ov01a1s);
++		pm_runtime_put(&client->dev);
++	}
++
++	ov01a1s->streaming = enable;
++	mutex_unlock(&ov01a1s->mutex);
++
++	return ret;
++}
++
++static int ov01a1s_power_off(struct device *dev)
++{
++	struct v4l2_subdev *sd = dev_get_drvdata(dev);
++	struct ov01a1s *ov01a1s = to_ov01a1s(sd);
++	int ret = 0;
++
++#if IS_ENABLED(CONFIG_INTEL_SKL_INT3472)
++	if (ov01a1s->power_type == OV01A1S_USE_INT3472) {
++		gpiod_set_value_cansleep(ov01a1s->reset_gpio, 1);
++		gpiod_set_value_cansleep(ov01a1s->powerdown_gpio, 1);
++		if (ov01a1s->avdd)
++			ret = regulator_disable(ov01a1s->avdd);
++		clk_disable_unprepare(ov01a1s->clk);
++		msleep(20);
++	}
++#elif IS_ENABLED(CONFIG_POWER_CTRL_LOGIC)
++	if (ov01a1s->power_type == OV01A1S_USE_INT3472)
++		ret = power_ctrl_logic_set_power(0);
++#endif
++#if IS_ENABLED(CONFIG_INTEL_VSC)
++	if (ov01a1s->power_type == OV01A1S_USE_INTEL_VSC) {
++		ret = vsc_release_camera_sensor(&ov01a1s->status);
++		if (ret && ret != -EAGAIN)
++			dev_err(dev, "Release VSC failed");
++	}
++#endif
++
++	return ret;
++}
++
++static int ov01a1s_power_on(struct device *dev)
++{
++	struct v4l2_subdev *sd = dev_get_drvdata(dev);
++	struct ov01a1s *ov01a1s = to_ov01a1s(sd);
++	int ret = 0;
++
++#if IS_ENABLED(CONFIG_INTEL_SKL_INT3472)
++	if (ov01a1s->power_type == OV01A1S_USE_INT3472) {
++		ret = clk_prepare_enable(ov01a1s->clk);
++		if (ret)
++			return ret;
++		if (ov01a1s->avdd)
++			ret = regulator_enable(ov01a1s->avdd);
++		if (ret)
++			return ret;
++		gpiod_set_value_cansleep(ov01a1s->powerdown_gpio, 0);
++		gpiod_set_value_cansleep(ov01a1s->reset_gpio, 0);
++		msleep(20);
++	}
++#elif IS_ENABLED(CONFIG_POWER_CTRL_LOGIC)
++	if (ov01a1s->power_type == OV01A1S_USE_INT3472)
++		ret = power_ctrl_logic_set_power(1);
++#endif
++#if IS_ENABLED(CONFIG_INTEL_VSC)
++	if (ov01a1s->power_type == OV01A1S_USE_INTEL_VSC) {
++		ret = vsc_acquire_camera_sensor(&ov01a1s->conf,
++						ov01a1s_vsc_privacy_callback,
++						ov01a1s, &ov01a1s->status);
++		if (ret && ret != -EAGAIN) {
++			dev_err(dev, "Acquire VSC failed");
++			return ret;
++		}
++		__v4l2_ctrl_s_ctrl(ov01a1s->privacy_status,
++				   !(ov01a1s->status.status));
++	}
++#endif
++
++	return ret;
++}
++
++static int __maybe_unused ov01a1s_suspend(struct device *dev)
++{
++	struct i2c_client *client = to_i2c_client(dev);
++	struct v4l2_subdev *sd = i2c_get_clientdata(client);
++	struct ov01a1s *ov01a1s = to_ov01a1s(sd);
++
++	mutex_lock(&ov01a1s->mutex);
++	if (ov01a1s->streaming)
++		ov01a1s_stop_streaming(ov01a1s);
++
++	mutex_unlock(&ov01a1s->mutex);
++
++	return 0;
++}
++
++static int __maybe_unused ov01a1s_resume(struct device *dev)
++{
++	struct i2c_client *client = to_i2c_client(dev);
++	struct v4l2_subdev *sd = i2c_get_clientdata(client);
++	struct ov01a1s *ov01a1s = to_ov01a1s(sd);
++	int ret = 0;
++
++	mutex_lock(&ov01a1s->mutex);
++	if (!ov01a1s->streaming)
++		goto exit;
++
++	ret = ov01a1s_start_streaming(ov01a1s);
++	if (ret) {
++		ov01a1s->streaming = false;
++		ov01a1s_stop_streaming(ov01a1s);
++	}
++
++exit:
++	mutex_unlock(&ov01a1s->mutex);
++	return ret;
++}
++
++static int ov01a1s_set_format(struct v4l2_subdev *sd,
++#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 14, 0)
++			      struct v4l2_subdev_pad_config *cfg,
++#else
++			      struct v4l2_subdev_state *sd_state,
++#endif
++			      struct v4l2_subdev_format *fmt)
++{
++	struct ov01a1s *ov01a1s = to_ov01a1s(sd);
++	const struct ov01a1s_mode *mode;
++	s32 vblank_def, h_blank;
++
++	mode = v4l2_find_nearest_size(supported_modes,
++				      ARRAY_SIZE(supported_modes), width,
++				      height, fmt->format.width,
++				      fmt->format.height);
++
++	mutex_lock(&ov01a1s->mutex);
++	ov01a1s_update_pad_format(mode, &fmt->format);
++	if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) {
++#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 14, 0)
++		*v4l2_subdev_get_try_format(sd, cfg, fmt->pad) = fmt->format;
++#else
++		*v4l2_subdev_get_try_format(sd, sd_state, fmt->pad) = fmt->format;
++#endif
++	} else {
++		ov01a1s->cur_mode = mode;
++		__v4l2_ctrl_s_ctrl(ov01a1s->link_freq, mode->link_freq_index);
++		__v4l2_ctrl_s_ctrl_int64(ov01a1s->pixel_rate, OV01A1S_SCLK);
++
++		/* Update limits and set FPS to default */
++		vblank_def = mode->vts_def - mode->height;
++		__v4l2_ctrl_modify_range(ov01a1s->vblank,
++					 mode->vts_min - mode->height,
++					 OV01A1S_VTS_MAX - mode->height, 1,
++					 vblank_def);
++		__v4l2_ctrl_s_ctrl(ov01a1s->vblank, vblank_def);
++		h_blank = mode->hts - mode->width;
++		__v4l2_ctrl_modify_range(ov01a1s->hblank, h_blank, h_blank, 1,
++					 h_blank);
++	}
++	mutex_unlock(&ov01a1s->mutex);
++
++	return 0;
++}
++
++static int ov01a1s_get_format(struct v4l2_subdev *sd,
++#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 14, 0)
++			      struct v4l2_subdev_pad_config *cfg,
++#else
++			      struct v4l2_subdev_state *sd_state,
++#endif
++			      struct v4l2_subdev_format *fmt)
++{
++	struct ov01a1s *ov01a1s = to_ov01a1s(sd);
++
++	mutex_lock(&ov01a1s->mutex);
++	if (fmt->which == V4L2_SUBDEV_FORMAT_TRY)
++#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 14, 0)
++		fmt->format = *v4l2_subdev_get_try_format(&ov01a1s->sd, cfg,
++							  fmt->pad);
++#else
++		fmt->format = *v4l2_subdev_get_try_format(&ov01a1s->sd,
++							  sd_state, fmt->pad);
++#endif
++	else
++		ov01a1s_update_pad_format(ov01a1s->cur_mode, &fmt->format);
++
++	mutex_unlock(&ov01a1s->mutex);
++
++	return 0;
++}
++
++static int ov01a1s_enum_mbus_code(struct v4l2_subdev *sd,
++#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 14, 0)
++				  struct v4l2_subdev_pad_config *cfg,
++#else
++				  struct v4l2_subdev_state *sd_state,
++#endif
++				  struct v4l2_subdev_mbus_code_enum *code)
++{
++	if (code->index > 0)
++		return -EINVAL;
++
++	code->code = MEDIA_BUS_FMT_SGRBG10_1X10;
++
++	return 0;
++}
++
++static int ov01a1s_enum_frame_size(struct v4l2_subdev *sd,
++#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 14, 0)
++				   struct v4l2_subdev_pad_config *cfg,
++#else
++				   struct v4l2_subdev_state *sd_state,
++#endif
++				   struct v4l2_subdev_frame_size_enum *fse)
++{
++	if (fse->index >= ARRAY_SIZE(supported_modes))
++		return -EINVAL;
++
++	if (fse->code != MEDIA_BUS_FMT_SGRBG10_1X10)
++		return -EINVAL;
++
++	fse->min_width = supported_modes[fse->index].width;
++	fse->max_width = fse->min_width;
++	fse->min_height = supported_modes[fse->index].height;
++	fse->max_height = fse->min_height;
++
++	return 0;
++}
++
++static int ov01a1s_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh)
++{
++	struct ov01a1s *ov01a1s = to_ov01a1s(sd);
++
++	mutex_lock(&ov01a1s->mutex);
++#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 14, 0)
++	ov01a1s_update_pad_format(&supported_modes[0],
++				  v4l2_subdev_get_try_format(sd, fh->pad, 0));
++#else
++	ov01a1s_update_pad_format(&supported_modes[0],
++				  v4l2_subdev_get_try_format(sd, fh->state, 0));
++#endif
++	mutex_unlock(&ov01a1s->mutex);
++
++	return 0;
++}
++
++static const struct v4l2_subdev_video_ops ov01a1s_video_ops = {
++	.s_stream = ov01a1s_set_stream,
++};
++
++static const struct v4l2_subdev_pad_ops ov01a1s_pad_ops = {
++	.set_fmt = ov01a1s_set_format,
++	.get_fmt = ov01a1s_get_format,
++	.enum_mbus_code = ov01a1s_enum_mbus_code,
++	.enum_frame_size = ov01a1s_enum_frame_size,
++};
++
++static const struct v4l2_subdev_ops ov01a1s_subdev_ops = {
++	.video = &ov01a1s_video_ops,
++	.pad = &ov01a1s_pad_ops,
++};
++
++static const struct media_entity_operations ov01a1s_subdev_entity_ops = {
++	.link_validate = v4l2_subdev_link_validate,
++};
++
++static const struct v4l2_subdev_internal_ops ov01a1s_internal_ops = {
++	.open = ov01a1s_open,
++};
++
++static int ov01a1s_identify_module(struct ov01a1s *ov01a1s)
++{
++	struct i2c_client *client = ov01a1s->client;
++	int ret;
++	u32 val;
++
++	ret = ov01a1s_read_reg(ov01a1s, OV01A1S_REG_CHIP_ID, 3, &val);
++	if (ret)
++		return ret;
++
++	if (val != OV01A1S_CHIP_ID) {
++		dev_err(&client->dev, "chip id mismatch: %x!=%x",
++			OV01A1S_CHIP_ID, val);
++		return -ENXIO;
++	}
++
++	return 0;
++}
++
++#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 1, 0)
++static int ov01a1s_remove(struct i2c_client *client)
++#else
++static void ov01a1s_remove(struct i2c_client *client)
++#endif
++{
++	struct v4l2_subdev *sd = i2c_get_clientdata(client);
++	struct ov01a1s *ov01a1s = to_ov01a1s(sd);
++
++	v4l2_async_unregister_subdev(sd);
++	media_entity_cleanup(&sd->entity);
++	v4l2_ctrl_handler_free(sd->ctrl_handler);
++	pm_runtime_disable(&client->dev);
++	mutex_destroy(&ov01a1s->mutex);
++
++#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 1, 0)
++	return 0;
++#endif
++}
++
++#if IS_ENABLED(CONFIG_INTEL_SKL_INT3472)
++static int ov01a1s_parse_gpio(struct ov01a1s *ov01a1s)
++{
++	struct device *dev = &ov01a1s->client->dev;
++
++	ov01a1s->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW);
++	if (IS_ERR(ov01a1s->reset_gpio)) {
++		dev_warn(dev, "error while getting reset gpio: %ld\n",
++			 PTR_ERR(ov01a1s->reset_gpio));
++		ov01a1s->reset_gpio = NULL;
++		return -EPROBE_DEFER;
++	}
++
++	/* For optional, don't return or print warn if can't get it */
++	ov01a1s->powerdown_gpio =
++		devm_gpiod_get_optional(dev, "powerdown", GPIOD_OUT_LOW);
++	if (IS_ERR(ov01a1s->powerdown_gpio)) {
++		dev_dbg(dev, "no powerdown gpio: %ld\n",
++			PTR_ERR(ov01a1s->powerdown_gpio));
++		ov01a1s->powerdown_gpio = NULL;
++	}
++
++	ov01a1s->avdd = devm_regulator_get_optional(dev, "avdd");
++	if (IS_ERR(ov01a1s->avdd)) {
++		dev_dbg(dev, "no regulator avdd: %ld\n",
++			PTR_ERR(ov01a1s->avdd));
++		ov01a1s->avdd = NULL;
++	}
++
++	ov01a1s->clk = devm_clk_get_optional(dev, "clk");
++	if (IS_ERR(ov01a1s->clk)) {
++		dev_dbg(dev, "no clk: %ld\n", PTR_ERR(ov01a1s->clk));
++		ov01a1s->clk = NULL;
++	}
++
++	return 0;
++}
++#endif
++
++static int ov01a1s_parse_power(struct ov01a1s *ov01a1s)
++{
++	int ret = 0;
++
++#if IS_ENABLED(CONFIG_INTEL_VSC)
++	ov01a1s->conf.lane_num = OV01A1S_DATA_LANES;
++	/* frequency unit 100k */
++	ov01a1s->conf.freq = OV01A1S_LINK_FREQ_400MHZ / 100000;
++	ret = vsc_acquire_camera_sensor(&ov01a1s->conf, NULL, NULL, &ov01a1s->status);
++	if (!ret) {
++		ov01a1s->power_type = OV01A1S_USE_INTEL_VSC;
++		return 0;
++	} else if (ret != -EAGAIN) {
++		return ret;
++	}
++#endif
++#if IS_ENABLED(CONFIG_INTEL_SKL_INT3472)
++	ret = ov01a1s_parse_gpio(ov01a1s);
++#elif IS_ENABLED(CONFIG_POWER_CTRL_LOGIC)
++	ret = power_ctrl_logic_set_power(1);
++#endif
++#if IS_ENABLED(CONFIG_INTEL_SKL_INT3472) || IS_ENABLED(CONFIG_POWER_CTRL_LOGIC)
++	if (!ret) {
++		ov01a1s->power_type = OV01A1S_USE_INT3472;
++		return 0;
++	}
++#endif
++	if (ret == -EAGAIN)
++		return -EPROBE_DEFER;
++
++	return ret;
++}
++
++static int ov01a1s_probe(struct i2c_client *client)
++{
++	struct ov01a1s *ov01a1s;
++	int ret = 0;
++
++	ov01a1s = devm_kzalloc(&client->dev, sizeof(*ov01a1s), GFP_KERNEL);
++	if (!ov01a1s)
++		return -ENOMEM;
++
++	ov01a1s->client = client;
++	ret = ov01a1s_parse_power(ov01a1s);
++	if (ret)
++		return ret;
++
++	v4l2_i2c_subdev_init(&ov01a1s->sd, client, &ov01a1s_subdev_ops);
++#if IS_ENABLED(CONFIG_INTEL_SKL_INT3472)
++	/* In other cases, power is up in ov01a1s_parse_power */
++	if (ov01a1s->power_type == OV01A1S_USE_INT3472)
++		ov01a1s_power_on(&client->dev);
++#endif
++	ret = ov01a1s_identify_module(ov01a1s);
++	if (ret) {
++		dev_err(&client->dev, "failed to find sensor: %d", ret);
++		goto probe_error_power_off;
++	}
++
++	mutex_init(&ov01a1s->mutex);
++	ov01a1s->cur_mode = &supported_modes[0];
++	ret = ov01a1s_init_controls(ov01a1s);
++	if (ret) {
++		dev_err(&client->dev, "failed to init controls: %d", ret);
++		goto probe_error_v4l2_ctrl_handler_free;
++	}
++
++	ov01a1s->sd.internal_ops = &ov01a1s_internal_ops;
++	ov01a1s->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE;
++	ov01a1s->sd.entity.ops = &ov01a1s_subdev_entity_ops;
++	ov01a1s->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR;
++	ov01a1s->pad.flags = MEDIA_PAD_FL_SOURCE;
++	ret = media_entity_pads_init(&ov01a1s->sd.entity, 1, &ov01a1s->pad);
++	if (ret) {
++		dev_err(&client->dev, "failed to init entity pads: %d", ret);
++		goto probe_error_v4l2_ctrl_handler_free;
++	}
++
++#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 13, 0)
++	ret = v4l2_async_register_subdev_sensor_common(&ov01a1s->sd);
++#else
++	ret = v4l2_async_register_subdev_sensor(&ov01a1s->sd);
++#endif
++	if (ret < 0) {
++		dev_err(&client->dev, "failed to register V4L2 subdev: %d",
++			ret);
++		goto probe_error_media_entity_cleanup;
++	}
++
++	/*
++	 * Device is already turned on by i2c-core with ACPI domain PM.
++	 * Enable runtime PM and turn off the device.
++	 */
++	pm_runtime_set_active(&client->dev);
++	pm_runtime_enable(&client->dev);
++	pm_runtime_idle(&client->dev);
++
++	return 0;
++
++probe_error_media_entity_cleanup:
++	media_entity_cleanup(&ov01a1s->sd.entity);
++
++probe_error_v4l2_ctrl_handler_free:
++	v4l2_ctrl_handler_free(ov01a1s->sd.ctrl_handler);
++	mutex_destroy(&ov01a1s->mutex);
++
++probe_error_power_off:
++	ov01a1s_power_off(&client->dev);
++
++	return ret;
++}
++
++static const struct dev_pm_ops ov01a1s_pm_ops = {
++	SET_SYSTEM_SLEEP_PM_OPS(ov01a1s_suspend, ov01a1s_resume)
++	SET_RUNTIME_PM_OPS(ov01a1s_power_off, ov01a1s_power_on, NULL)
++};
++
++#ifdef CONFIG_ACPI
++static const struct acpi_device_id ov01a1s_acpi_ids[] = {
++	{ "OVTI01AS" },
++	{}
++};
++
++MODULE_DEVICE_TABLE(acpi, ov01a1s_acpi_ids);
++#endif
++
++static struct i2c_driver ov01a1s_i2c_driver = {
++	.driver = {
++		.name = "ov01a1s",
++		.pm = &ov01a1s_pm_ops,
++		.acpi_match_table = ACPI_PTR(ov01a1s_acpi_ids),
++	},
++#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 6, 0)
++	.probe_new = ov01a1s_probe,
++#else
++	.probe = ov01a1s_probe,
++#endif
++	.remove = ov01a1s_remove,
++};
++
++module_i2c_driver(ov01a1s_i2c_driver);
++
++MODULE_AUTHOR("Xu, Chongyang <chongyang.xu@intel.com>");
++MODULE_AUTHOR("Lai, Jim <jim.lai@intel.com>");
++MODULE_AUTHOR("Qiu, Tianshu <tian.shu.qiu@intel.com>");
++MODULE_AUTHOR("Shawn Tu <shawnx.tu@intel.com>");
++MODULE_AUTHOR("Bingbu Cao <bingbu.cao@intel.com>");
++MODULE_DESCRIPTION("OmniVision OV01A1S sensor driver");
++MODULE_LICENSE("GPL v2");
+-- 
+2.43.2
+
+
+From 9f58ae728245ad7ac604737ab16781d7ccb2006e Mon Sep 17 00:00:00 2001
+From: Florian Klink <flokli@flokli.de>
+Date: Sun, 17 Mar 2024 14:24:05 +0200
+Subject: [PATCH 27/33] ov01a1s.c: support Linux 6.8.0
+
+Used https://github.com/intel/ipu6-drivers/pull/213 as an inspiration.
+---
+ drivers/media/i2c/ov01a1s.c | 13 ++++++++++---
+ 1 file changed, 10 insertions(+), 3 deletions(-)
+
+diff --git a/drivers/media/i2c/ov01a1s.c b/drivers/media/i2c/ov01a1s.c
+index 0dcce8b492b4..923b12b2a948 100644
+--- a/drivers/media/i2c/ov01a1s.c
++++ b/drivers/media/i2c/ov01a1s.c
+@@ -832,8 +832,10 @@ static int ov01a1s_set_format(struct v4l2_subdev *sd,
+ 	if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) {
+ #if LINUX_VERSION_CODE < KERNEL_VERSION(5, 14, 0)
+ 		*v4l2_subdev_get_try_format(sd, cfg, fmt->pad) = fmt->format;
+-#else
++#elif LINUX_VERSION_CODE < KERNEL_VERSION(6, 8, 0)
+ 		*v4l2_subdev_get_try_format(sd, sd_state, fmt->pad) = fmt->format;
++#else
++		*v4l2_subdev_state_get_format(sd_state, fmt->pad) = fmt->format;
+ #endif
+ 	} else {
+ 		ov01a1s->cur_mode = mode;
+@@ -871,9 +873,11 @@ static int ov01a1s_get_format(struct v4l2_subdev *sd,
+ #if LINUX_VERSION_CODE < KERNEL_VERSION(5, 14, 0)
+ 		fmt->format = *v4l2_subdev_get_try_format(&ov01a1s->sd, cfg,
+ 							  fmt->pad);
+-#else
++#elif LINUX_VERSION_CODE < KERNEL_VERSION(6, 8, 0)
+ 		fmt->format = *v4l2_subdev_get_try_format(&ov01a1s->sd,
+ 							  sd_state, fmt->pad);
++#else
++		fmt->format = *v4l2_subdev_state_get_format(sd_state, fmt->pad);
+ #endif
+ 	else
+ 		ov01a1s_update_pad_format(ov01a1s->cur_mode, &fmt->format);
+@@ -929,9 +933,12 @@ static int ov01a1s_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh)
+ #if LINUX_VERSION_CODE < KERNEL_VERSION(5, 14, 0)
+ 	ov01a1s_update_pad_format(&supported_modes[0],
+ 				  v4l2_subdev_get_try_format(sd, fh->pad, 0));
+-#else
++#elif LINUX_VERSION_CODE < KERNEL_VERSION(6, 8, 0)
+ 	ov01a1s_update_pad_format(&supported_modes[0],
+ 				  v4l2_subdev_get_try_format(sd, fh->state, 0));
++#else
++	ov01a1s_update_pad_format(&supported_modes[0],
++				  v4l2_subdev_state_get_format(fh->state, 0));
+ #endif
+ 	mutex_unlock(&ov01a1s->mutex);
+ 
+-- 
+2.43.2
+
+
+From 80bee1ca899ebfa4126d1e69ea821a2c30aba00c Mon Sep 17 00:00:00 2001
+From: Hans de Goede <hdegoede@redhat.com>
+Date: Mon, 6 Nov 2023 12:33:56 +0100
+Subject: [PATCH 28/33] media: ov01a1s: Remove non upstream iVSC support
+
+Signed-off-by: Hans de Goede <hdegoede@redhat.com>
+---
+ drivers/media/i2c/ov01a1s.c | 71 -------------------------------------
+ 1 file changed, 71 deletions(-)
+
+diff --git a/drivers/media/i2c/ov01a1s.c b/drivers/media/i2c/ov01a1s.c
+index 923b12b2a948..22b406bdeae9 100644
+--- a/drivers/media/i2c/ov01a1s.c
++++ b/drivers/media/i2c/ov01a1s.c
+@@ -17,9 +17,6 @@
+ #elif IS_ENABLED(CONFIG_POWER_CTRL_LOGIC)
+ #include "power_ctrl_logic.h"
+ #endif
+-#if IS_ENABLED(CONFIG_INTEL_VSC)
+-#include <linux/vsc.h>
+-#endif
+ 
+ #define OV01A1S_LINK_FREQ_400MHZ	400000000ULL
+ #define OV01A1S_SCLK			40000000LL
+@@ -302,13 +299,6 @@ struct ov01a1s {
+ 	struct v4l2_ctrl *vblank;
+ 	struct v4l2_ctrl *hblank;
+ 	struct v4l2_ctrl *exposure;
+-#if IS_ENABLED(CONFIG_INTEL_VSC)
+-	struct v4l2_ctrl *privacy_status;
+-
+-	/* VSC settings */
+-	struct vsc_mipi_config conf;
+-	struct vsc_camera_status status;
+-#endif
+ 
+ 	/* Current mode */
+ 	const struct ov01a1s_mode *cur_mode;
+@@ -334,9 +324,6 @@ struct ov01a1s {
+ 		OV01A1S_USE_DEFAULT = 0,
+ #if IS_ENABLED(CONFIG_INTEL_SKL_INT3472) || IS_ENABLED(CONFIG_POWER_CTRL_LOGIC)
+ 		OV01A1S_USE_INT3472 = 1,
+-#endif
+-#if IS_ENABLED(CONFIG_INTEL_VSC)
+-		OV01A1S_USE_INTEL_VSC = 2,
+ #endif
+ 	} power_type;
+ 
+@@ -505,12 +492,6 @@ static int ov01a1s_set_ctrl(struct v4l2_ctrl *ctrl)
+ 		ret = ov01a1s_test_pattern(ov01a1s, ctrl->val);
+ 		break;
+ 
+-#if IS_ENABLED(CONFIG_INTEL_VSC)
+-	case V4L2_CID_PRIVACY:
+-		dev_dbg(&client->dev, "set privacy to %d", ctrl->val);
+-		break;
+-
+-#endif
+ 	default:
+ 		ret = -EINVAL;
+ 		break;
+@@ -535,11 +516,7 @@ static int ov01a1s_init_controls(struct ov01a1s *ov01a1s)
+ 	int ret = 0;
+ 
+ 	ctrl_hdlr = &ov01a1s->ctrl_handler;
+-#if IS_ENABLED(CONFIG_INTEL_VSC)
+-	ret = v4l2_ctrl_handler_init(ctrl_hdlr, 9);
+-#else
+ 	ret = v4l2_ctrl_handler_init(ctrl_hdlr, 8);
+-#endif
+ 	if (ret)
+ 		return ret;
+ 
+@@ -572,12 +549,6 @@ static int ov01a1s_init_controls(struct ov01a1s *ov01a1s)
+ 					    1, h_blank);
+ 	if (ov01a1s->hblank)
+ 		ov01a1s->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY;
+-#if IS_ENABLED(CONFIG_INTEL_VSC)
+-	ov01a1s->privacy_status = v4l2_ctrl_new_std(ctrl_hdlr,
+-						    &ov01a1s_ctrl_ops,
+-						    V4L2_CID_PRIVACY,
+-						    0, 1, 1, 0);
+-#endif
+ 
+ 	v4l2_ctrl_new_std(ctrl_hdlr, &ov01a1s_ctrl_ops, V4L2_CID_ANALOGUE_GAIN,
+ 			  OV01A1S_ANAL_GAIN_MIN, OV01A1S_ANAL_GAIN_MAX,
+@@ -613,16 +584,6 @@ static void ov01a1s_update_pad_format(const struct ov01a1s_mode *mode,
+ 	fmt->field = V4L2_FIELD_NONE;
+ }
+ 
+-#if IS_ENABLED(CONFIG_INTEL_VSC)
+-static void ov01a1s_vsc_privacy_callback(void *handle,
+-				       enum vsc_privacy_status status)
+-{
+-	struct ov01a1s *ov01a1s = handle;
+-
+-	v4l2_ctrl_s_ctrl(ov01a1s->privacy_status, !status);
+-}
+-
+-#endif
+ static int ov01a1s_start_streaming(struct ov01a1s *ov01a1s)
+ {
+ 	struct i2c_client *client = ov01a1s->client;
+@@ -722,13 +683,6 @@ static int ov01a1s_power_off(struct device *dev)
+ 	if (ov01a1s->power_type == OV01A1S_USE_INT3472)
+ 		ret = power_ctrl_logic_set_power(0);
+ #endif
+-#if IS_ENABLED(CONFIG_INTEL_VSC)
+-	if (ov01a1s->power_type == OV01A1S_USE_INTEL_VSC) {
+-		ret = vsc_release_camera_sensor(&ov01a1s->status);
+-		if (ret && ret != -EAGAIN)
+-			dev_err(dev, "Release VSC failed");
+-	}
+-#endif
+ 
+ 	return ret;
+ }
+@@ -756,19 +710,6 @@ static int ov01a1s_power_on(struct device *dev)
+ 	if (ov01a1s->power_type == OV01A1S_USE_INT3472)
+ 		ret = power_ctrl_logic_set_power(1);
+ #endif
+-#if IS_ENABLED(CONFIG_INTEL_VSC)
+-	if (ov01a1s->power_type == OV01A1S_USE_INTEL_VSC) {
+-		ret = vsc_acquire_camera_sensor(&ov01a1s->conf,
+-						ov01a1s_vsc_privacy_callback,
+-						ov01a1s, &ov01a1s->status);
+-		if (ret && ret != -EAGAIN) {
+-			dev_err(dev, "Acquire VSC failed");
+-			return ret;
+-		}
+-		__v4l2_ctrl_s_ctrl(ov01a1s->privacy_status,
+-				   !(ov01a1s->status.status));
+-	}
+-#endif
+ 
+ 	return ret;
+ }
+@@ -1051,18 +992,6 @@ static int ov01a1s_parse_power(struct ov01a1s *ov01a1s)
+ {
+ 	int ret = 0;
+ 
+-#if IS_ENABLED(CONFIG_INTEL_VSC)
+-	ov01a1s->conf.lane_num = OV01A1S_DATA_LANES;
+-	/* frequency unit 100k */
+-	ov01a1s->conf.freq = OV01A1S_LINK_FREQ_400MHZ / 100000;
+-	ret = vsc_acquire_camera_sensor(&ov01a1s->conf, NULL, NULL, &ov01a1s->status);
+-	if (!ret) {
+-		ov01a1s->power_type = OV01A1S_USE_INTEL_VSC;
+-		return 0;
+-	} else if (ret != -EAGAIN) {
+-		return ret;
+-	}
+-#endif
+ #if IS_ENABLED(CONFIG_INTEL_SKL_INT3472)
+ 	ret = ov01a1s_parse_gpio(ov01a1s);
+ #elif IS_ENABLED(CONFIG_POWER_CTRL_LOGIC)
+-- 
+2.43.2
+
+
+From e624515c64d782b452a4676c1e117815267559ae Mon Sep 17 00:00:00 2001
+From: Hans de Goede <hdegoede@redhat.com>
+Date: Tue, 23 Jan 2024 14:58:35 +0100
+Subject: [PATCH 29/33] media: hi556: Return -EPROBE_DEFER if no endpoint is
+ found
+
+With ipu bridge, endpoints may only be created when ipu bridge has
+initialised. This may happen after the sensor driver has first probed.
+
+Signed-off-by: Hans de Goede <hdegoede@redhat.com>
+---
+ drivers/media/i2c/hi556.c | 13 +++++++------
+ 1 file changed, 7 insertions(+), 6 deletions(-)
+
+diff --git a/drivers/media/i2c/hi556.c b/drivers/media/i2c/hi556.c
+index 38c77d515786..96bae9914d52 100644
+--- a/drivers/media/i2c/hi556.c
++++ b/drivers/media/i2c/hi556.c
+@@ -1206,8 +1206,13 @@ static int hi556_check_hwcfg(struct device *dev)
+ 	int ret = 0;
+ 	unsigned int i, j;
+ 
+-	if (!fwnode)
+-		return -ENXIO;
++	/*
++	 * Sometimes the fwnode graph is initialized by the bridge driver,
++	 * wait for this.
++	 */
++	ep = fwnode_graph_get_next_endpoint(fwnode, NULL);
++	if (!ep)
++		return -EPROBE_DEFER;
+ 
+ 	ret = fwnode_property_read_u32(fwnode, "clock-frequency", &mclk);
+ 	if (ret) {
+@@ -1220,10 +1225,6 @@ static int hi556_check_hwcfg(struct device *dev)
+ 		return -EINVAL;
+ 	}
+ 
+-	ep = fwnode_graph_get_next_endpoint(fwnode, NULL);
+-	if (!ep)
+-		return -ENXIO;
+-
+ 	ret = v4l2_fwnode_endpoint_alloc_parse(ep, &bus_cfg);
+ 	fwnode_handle_put(ep);
+ 	if (ret)
+-- 
+2.43.2
+
+
+From b127d1003050fb894ea764b600d5f399af413b68 Mon Sep 17 00:00:00 2001
+From: Hans de Goede <hdegoede@redhat.com>
+Date: Tue, 23 Jan 2024 14:48:26 +0100
+Subject: [PATCH 30/33] media: hi556: Add support for reset GPIO
+
+On some ACPI platforms, such as Chromebooks the ACPI methods to
+change the power-state (_PS0 and _PS3) fully take care of powering
+on/off the sensor.
+
+On other ACPI platforms, such as e.g. various HP models with IPU6 +
+hi556 sensor, the sensor driver must control the reset GPIO itself.
+
+Add support for having the driver control an optional reset GPIO.
+
+Signed-off-by: Hans de Goede <hdegoede@redhat.com>
+---
+ drivers/media/i2c/hi556.c | 45 ++++++++++++++++++++++++++++++++++++++-
+ 1 file changed, 44 insertions(+), 1 deletion(-)
+
+diff --git a/drivers/media/i2c/hi556.c b/drivers/media/i2c/hi556.c
+index 96bae9914d52..f5a39b83598b 100644
+--- a/drivers/media/i2c/hi556.c
++++ b/drivers/media/i2c/hi556.c
+@@ -4,6 +4,7 @@
+ #include <asm/unaligned.h>
+ #include <linux/acpi.h>
+ #include <linux/delay.h>
++#include <linux/gpio/consumer.h>
+ #include <linux/i2c.h>
+ #include <linux/module.h>
+ #include <linux/pm_runtime.h>
+@@ -633,6 +634,9 @@ struct hi556 {
+ 	struct v4l2_ctrl *hblank;
+ 	struct v4l2_ctrl *exposure;
+ 
++	/* GPIOs, clocks, etc. */
++	struct gpio_desc *reset_gpio;
++
+ 	/* Current mode */
+ 	const struct hi556_mode *cur_mode;
+ 
+@@ -1276,6 +1280,25 @@ static void hi556_remove(struct i2c_client *client)
+ 	mutex_destroy(&hi556->mutex);
+ }
+ 
++static int hi556_suspend(struct device *dev)
++{
++	struct v4l2_subdev *sd = dev_get_drvdata(dev);
++	struct hi556 *hi556 = to_hi556(sd);
++
++	gpiod_set_value_cansleep(hi556->reset_gpio, 1);
++	return 0;
++}
++
++static int hi556_resume(struct device *dev)
++{
++	struct v4l2_subdev *sd = dev_get_drvdata(dev);
++	struct hi556 *hi556 = to_hi556(sd);
++
++	gpiod_set_value_cansleep(hi556->reset_gpio, 0);
++	usleep_range(5000, 5500);
++	return 0;
++}
++
+ static int hi556_probe(struct i2c_client *client)
+ {
+ 	struct hi556 *hi556;
+@@ -1295,12 +1318,24 @@ static int hi556_probe(struct i2c_client *client)
+ 
+ 	v4l2_i2c_subdev_init(&hi556->sd, client, &hi556_subdev_ops);
+ 
++	hi556->reset_gpio = devm_gpiod_get_optional(&client->dev, "reset",
++						    GPIOD_OUT_HIGH);
++	if (IS_ERR(hi556->reset_gpio))
++		return dev_err_probe(&client->dev, PTR_ERR(hi556->reset_gpio),
++				     "failed to get reset GPIO\n");
++
+ 	full_power = acpi_dev_state_d0(&client->dev);
+ 	if (full_power) {
++		/* Ensure non ACPI managed resources are enabled */
++		ret = hi556_resume(&client->dev);
++		if (ret)
++			return dev_err_probe(&client->dev, ret,
++					     "failed to power on sensor\n");
++
+ 		ret = hi556_identify_module(hi556);
+ 		if (ret) {
+ 			dev_err(&client->dev, "failed to find sensor: %d", ret);
+-			return ret;
++			goto probe_error_power_off;
+ 		}
+ 	}
+ 
+@@ -1345,9 +1380,16 @@ static int hi556_probe(struct i2c_client *client)
+ 	v4l2_ctrl_handler_free(hi556->sd.ctrl_handler);
+ 	mutex_destroy(&hi556->mutex);
+ 
++probe_error_power_off:
++	if (full_power)
++		hi556_suspend(&client->dev);
++
+ 	return ret;
+ }
+ 
++static DEFINE_RUNTIME_DEV_PM_OPS(hi556_pm_ops, hi556_suspend, hi556_resume,
++				 NULL);
++
+ #ifdef CONFIG_ACPI
+ static const struct acpi_device_id hi556_acpi_ids[] = {
+ 	{"INT3537"},
+@@ -1361,6 +1403,7 @@ static struct i2c_driver hi556_i2c_driver = {
+ 	.driver = {
+ 		.name = "hi556",
+ 		.acpi_match_table = ACPI_PTR(hi556_acpi_ids),
++		.pm = pm_sleep_ptr(&hi556_pm_ops),
+ 	},
+ 	.probe = hi556_probe,
+ 	.remove = hi556_remove,
+-- 
+2.43.2
+
+
+From ee651202ba2ca38da067b5379edd7b4f339cf7a8 Mon Sep 17 00:00:00 2001
+From: Hans de Goede <hdegoede@redhat.com>
+Date: Tue, 23 Jan 2024 14:54:22 +0100
+Subject: [PATCH 31/33] media: hi556: Add support for external clock
+
+On some ACPI platforms, such as Chromebooks the ACPI methods to
+change the power-state (_PS0 and _PS3) fully take care of powering
+on/off the sensor.
+
+On other ACPI platforms, such as e.g. various HP models with IPU6 +
+hi556 sensor, the sensor driver must control the sensor's clock itself.
+
+Add support for having the driver control an optional clock.
+
+Signed-off-by: Hans de Goede <hdegoede@redhat.com>
+---
+ drivers/media/i2c/hi556.c | 13 +++++++++++++
+ 1 file changed, 13 insertions(+)
+
+diff --git a/drivers/media/i2c/hi556.c b/drivers/media/i2c/hi556.c
+index f5a39b83598b..b783e0f56687 100644
+--- a/drivers/media/i2c/hi556.c
++++ b/drivers/media/i2c/hi556.c
+@@ -3,6 +3,7 @@
+ 
+ #include <asm/unaligned.h>
+ #include <linux/acpi.h>
++#include <linux/clk.h>
+ #include <linux/delay.h>
+ #include <linux/gpio/consumer.h>
+ #include <linux/i2c.h>
+@@ -636,6 +637,7 @@ struct hi556 {
+ 
+ 	/* GPIOs, clocks, etc. */
+ 	struct gpio_desc *reset_gpio;
++	struct clk *clk;
+ 
+ 	/* Current mode */
+ 	const struct hi556_mode *cur_mode;
+@@ -1286,6 +1288,7 @@ static int hi556_suspend(struct device *dev)
+ 	struct hi556 *hi556 = to_hi556(sd);
+ 
+ 	gpiod_set_value_cansleep(hi556->reset_gpio, 1);
++	clk_disable_unprepare(hi556->clk);
+ 	return 0;
+ }
+ 
+@@ -1293,6 +1296,11 @@ static int hi556_resume(struct device *dev)
+ {
+ 	struct v4l2_subdev *sd = dev_get_drvdata(dev);
+ 	struct hi556 *hi556 = to_hi556(sd);
++	int ret;
++
++	ret = clk_prepare_enable(hi556->clk);
++	if (ret)
++		return ret;
+ 
+ 	gpiod_set_value_cansleep(hi556->reset_gpio, 0);
+ 	usleep_range(5000, 5500);
+@@ -1324,6 +1332,11 @@ static int hi556_probe(struct i2c_client *client)
+ 		return dev_err_probe(&client->dev, PTR_ERR(hi556->reset_gpio),
+ 				     "failed to get reset GPIO\n");
+ 
++	hi556->clk = devm_clk_get_optional(&client->dev, "clk");
++	if (IS_ERR(hi556->clk))
++		return dev_err_probe(&client->dev, PTR_ERR(hi556->clk),
++				     "failed to get clock\n");
++
+ 	full_power = acpi_dev_state_d0(&client->dev);
+ 	if (full_power) {
+ 		/* Ensure non ACPI managed resources are enabled */
+-- 
+2.43.2
+
+
+From 16be71996d451b8137ba63070e760448814c11a1 Mon Sep 17 00:00:00 2001
+From: Hans de Goede <hdegoede@redhat.com>
+Date: Wed, 24 Jan 2024 18:45:02 +0100
+Subject: [PATCH 32/33] media: hi556: Add support for avdd regulator
+
+On some ACPI platforms, such as Chromebooks the ACPI methods to
+change the power-state (_PS0 and _PS3) fully take care of powering
+on/off the sensor.
+
+On other ACPI platforms, such as e.g. various HP models with IPU6 +
+hi556 sensor, the sensor driver must control the avdd regulator itself.
+
+Add support for having the driver control the sensor's avdd regulator.
+Note this relies on the regulator-core providing a dummy regulator
+(which it does by default) on platforms where Linux is not aware of
+the avdd regulator.
+
+Signed-off-by: Hans de Goede <hdegoede@redhat.com>
+---
+ drivers/media/i2c/hi556.c | 24 ++++++++++++++++++++++++
+ 1 file changed, 24 insertions(+)
+
+diff --git a/drivers/media/i2c/hi556.c b/drivers/media/i2c/hi556.c
+index b783e0f56687..5641c249d4b1 100644
+--- a/drivers/media/i2c/hi556.c
++++ b/drivers/media/i2c/hi556.c
+@@ -9,6 +9,7 @@
+ #include <linux/i2c.h>
+ #include <linux/module.h>
+ #include <linux/pm_runtime.h>
++#include <linux/regulator/consumer.h>
+ #include <media/v4l2-ctrls.h>
+ #include <media/v4l2-device.h>
+ #include <media/v4l2-fwnode.h>
+@@ -638,6 +639,7 @@ struct hi556 {
+ 	/* GPIOs, clocks, etc. */
+ 	struct gpio_desc *reset_gpio;
+ 	struct clk *clk;
++	struct regulator *avdd;
+ 
+ 	/* Current mode */
+ 	const struct hi556_mode *cur_mode;
+@@ -1286,8 +1288,17 @@ static int hi556_suspend(struct device *dev)
+ {
+ 	struct v4l2_subdev *sd = dev_get_drvdata(dev);
+ 	struct hi556 *hi556 = to_hi556(sd);
++	int ret;
+ 
+ 	gpiod_set_value_cansleep(hi556->reset_gpio, 1);
++
++	ret = regulator_disable(hi556->avdd);
++	if (ret) {
++		dev_err(dev, "failed to disable avdd: %d\n", ret);
++		gpiod_set_value_cansleep(hi556->reset_gpio, 0);
++		return ret;
++	}
++
+ 	clk_disable_unprepare(hi556->clk);
+ 	return 0;
+ }
+@@ -1302,6 +1313,13 @@ static int hi556_resume(struct device *dev)
+ 	if (ret)
+ 		return ret;
+ 
++	ret = regulator_enable(hi556->avdd);
++	if (ret) {
++		dev_err(dev, "failed to enable avdd: %d\n", ret);
++		clk_disable_unprepare(hi556->clk);
++		return ret;
++	}
++
+ 	gpiod_set_value_cansleep(hi556->reset_gpio, 0);
+ 	usleep_range(5000, 5500);
+ 	return 0;
+@@ -1337,6 +1355,12 @@ static int hi556_probe(struct i2c_client *client)
+ 		return dev_err_probe(&client->dev, PTR_ERR(hi556->clk),
+ 				     "failed to get clock\n");
+ 
++	/* The regulator core will provide a "dummy" regulator if necessary */
++	hi556->avdd = devm_regulator_get(&client->dev, "avdd");
++	if (IS_ERR(hi556->avdd))
++		return dev_err_probe(&client->dev, PTR_ERR(hi556->avdd),
++				     "failed to get avdd regulator\n");
++
+ 	full_power = acpi_dev_state_d0(&client->dev);
+ 	if (full_power) {
+ 		/* Ensure non ACPI managed resources are enabled */
+-- 
+2.43.2
+
+
+From 6bd6e73829cf264120f629c88c552c4eb59c7eee Mon Sep 17 00:00:00 2001
+From: Florian Klink <flokli@flokli.de>
+Date: Sun, 17 Mar 2024 17:07:53 +0200
+Subject: [PATCH 33/33] media: intel/ipu6: fix firmware paths
+
+linux-firmware ships them in intel/ipu, not intel/.
+---
+ drivers/media/pci/intel/ipu6/ipu6.h | 8 ++++----
+ 1 file changed, 4 insertions(+), 4 deletions(-)
+
+diff --git a/drivers/media/pci/intel/ipu6/ipu6.h b/drivers/media/pci/intel/ipu6/ipu6.h
+index 04e7e7e61ca5..da8a95a9edf8 100644
+--- a/drivers/media/pci/intel/ipu6/ipu6.h
++++ b/drivers/media/pci/intel/ipu6/ipu6.h
+@@ -24,10 +24,10 @@ struct ipu6_bus_device;
+ #define IPU6_NAME			"intel-ipu6"
+ #define IPU6_MEDIA_DEV_MODEL_NAME	"ipu6"
+ 
+-#define IPU6SE_FIRMWARE_NAME		"intel/ipu6se_fw.bin"
+-#define IPU6EP_FIRMWARE_NAME		"intel/ipu6ep_fw.bin"
+-#define IPU6_FIRMWARE_NAME		"intel/ipu6_fw.bin"
+-#define IPU6EPMTL_FIRMWARE_NAME		"intel/ipu6epmtl_fw.bin"
++#define IPU6SE_FIRMWARE_NAME		"intel/ipu/ipu6se_fw.bin"
++#define IPU6EP_FIRMWARE_NAME		"intel/ipu/ipu6ep_fw.bin"
++#define IPU6_FIRMWARE_NAME		"intel/ipu/ipu6_fw.bin"
++#define IPU6EPMTL_FIRMWARE_NAME		"intel/ipu/ipu6epmtl_fw.bin"
+ 
+ enum ipu6_version {
+ 	IPU6_VER_INVALID = 0,
+-- 
+2.43.2
+
diff --git a/users/flokli/ipu6-softisp/libcamera/.skip-tree b/users/flokli/ipu6-softisp/libcamera/.skip-tree
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/users/flokli/ipu6-softisp/libcamera/.skip-tree
diff --git a/users/flokli/ipu6-softisp/libcamera/0001-libcamera-pipeline-simple-fix-size-adjustment-in-val.patch b/users/flokli/ipu6-softisp/libcamera/0001-libcamera-pipeline-simple-fix-size-adjustment-in-val.patch
new file mode 100644
index 0000000000..b640ddaa24
--- /dev/null
+++ b/users/flokli/ipu6-softisp/libcamera/0001-libcamera-pipeline-simple-fix-size-adjustment-in-val.patch
@@ -0,0 +1,82 @@
+From d86746fc1739f678e4bafe43f5047cba9b6b053e Mon Sep 17 00:00:00 2001
+From: Andrey Konovalov <andrey.konovalov@linaro.org>
+Date: Mon, 11 Mar 2024 15:15:05 +0100
+Subject: [PATCH 01/21] 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.
+
+Tested-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org> # sc8280xp Lenovo x13s
+Tested-by: Pavel Machek <pavel@ucw.cz>
+Reviewed-by: Milan Zamazal <mzamazal@redhat.com>
+Reviewed-by: Pavel Machek <pavel@ucw.cz>
+Signed-off-by: Andrey Konovalov <andrey.konovalov@linaro.org>
+Signed-off-by: Hans de Goede <hdegoede@redhat.com>
+---
+ src/libcamera/pipeline/simple/simple.cpp | 37 ++++++++++++++++++++++--
+ 1 file changed, 35 insertions(+), 2 deletions(-)
+
+diff --git a/src/libcamera/pipeline/simple/simple.cpp b/src/libcamera/pipeline/simple/simple.cpp
+index 911051b2..a84f6760 100644
+--- a/src/libcamera/pipeline/simple/simple.cpp
++++ b/src/libcamera/pipeline/simple/simple.cpp
+@@ -881,6 +881,30 @@ SimpleCameraConfiguration::SimpleCameraConfiguration(Camera *camera,
+ {
+ }
+ 
++namespace {
++
++static Size adjustSize(const Size &requestedSize, const SizeRange &supportedSizes)
++{
++	ASSERT(supportedSizes.min <= supportedSizes.max);
++
++	if (supportedSizes.min == supportedSizes.max)
++		return supportedSizes.max;
++
++	unsigned int hStep = supportedSizes.hStep;
++	unsigned int vStep = supportedSizes.vStep;
++
++	if (hStep == 0)
++		hStep = supportedSizes.max.width - supportedSizes.min.width;
++	if (vStep == 0)
++		vStep = supportedSizes.max.height - supportedSizes.min.height;
++
++	Size adjusted = requestedSize.boundedTo(supportedSizes.max).expandedTo(supportedSizes.min);
++
++	return adjusted.shrunkBy(supportedSizes.min).alignedDownTo(hStep, vStep).grownBy(supportedSizes.min);
++}
++
++} /* namespace */
++
+ CameraConfiguration::Status SimpleCameraConfiguration::validate()
+ {
+ 	const CameraSensor *sensor = data_->sensor_.get();
+@@ -997,10 +1021,19 @@ CameraConfiguration::Status SimpleCameraConfiguration::validate()
+ 		}
+ 
+ 		if (!pipeConfig_->outputSizes.contains(cfg.size)) {
++			Size adjustedSize = pipeConfig_->captureSize;
++			/*
++			 * The converter (when present) may not be able to output
++			 * a size identical to its input size. The capture size is thus
++			 * not guaranteed to be a valid output size. In such cases, use
++			 * the smaller valid output size closest to the requested.
++			 */
++			if (!pipeConfig_->outputSizes.contains(adjustedSize))
++				adjustedSize = adjustSize(cfg.size, pipeConfig_->outputSizes);
+ 			LOG(SimplePipeline, Debug)
+ 				<< "Adjusting size from " << cfg.size
+-				<< " to " << pipeConfig_->captureSize;
+-			cfg.size = pipeConfig_->captureSize;
++				<< " to " << adjustedSize;
++			cfg.size = adjustedSize;
+ 			status = Adjusted;
+ 		}
+ 
+-- 
+2.43.2
+
diff --git a/users/flokli/ipu6-softisp/libcamera/0002-libcamera-internal-Move-dma_heaps.-h-cpp-to-common-d.patch b/users/flokli/ipu6-softisp/libcamera/0002-libcamera-internal-Move-dma_heaps.-h-cpp-to-common-d.patch
new file mode 100644
index 0000000000..450a0a21f1
--- /dev/null
+++ b/users/flokli/ipu6-softisp/libcamera/0002-libcamera-internal-Move-dma_heaps.-h-cpp-to-common-d.patch
@@ -0,0 +1,350 @@
+From 96e50c6a43352a9cb81d558fea27e580f2b26585 Mon Sep 17 00:00:00 2001
+From: Andrey Konovalov <andrey.konovalov@linaro.org>
+Date: Mon, 11 Mar 2024 15:15:06 +0100
+Subject: [PATCH 02/21] 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.
+
+Tested-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org> # sc8280xp Lenovo x13s
+Tested-by: Pavel Machek <pavel@ucw.cz>
+Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
+Reviewed-by: Naushir Patuck <naush@raspberrypi.com>
+Reviewed-by: Pavel Machek <pavel@ucw.cz>
+Signed-off-by: Andrey Konovalov <andrey.konovalov@linaro.org>
+Signed-off-by: Hans de Goede <hdegoede@redhat.com>
+Reviewed-by: Milan Zamazal <mzamazal@redhat.com>
+---
+ .../libcamera/internal}/dma_heaps.h           |   4 -
+ include/libcamera/internal/meson.build        |   1 +
+ src/libcamera/dma_heaps.cpp                   | 127 ++++++++++++++++++
+ src/libcamera/meson.build                     |   1 +
+ src/libcamera/pipeline/rpi/vc4/dma_heaps.cpp  |  90 -------------
+ src/libcamera/pipeline/rpi/vc4/meson.build    |   1 -
+ src/libcamera/pipeline/rpi/vc4/vc4.cpp        |   5 +-
+ 7 files changed, 131 insertions(+), 98 deletions(-)
+ rename {src/libcamera/pipeline/rpi/vc4 => include/libcamera/internal}/dma_heaps.h (92%)
+ create mode 100644 src/libcamera/dma_heaps.cpp
+ delete mode 100644 src/libcamera/pipeline/rpi/vc4/dma_heaps.cpp
+
+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/dma_heaps.cpp b/src/libcamera/dma_heaps.cpp
+new file mode 100644
+index 00000000..38ef175a
+--- /dev/null
++++ b/src/libcamera/dma_heaps.cpp
+@@ -0,0 +1,127 @@
++/* SPDX-License-Identifier: LGPL-2.1-or-later */
++/*
++ * Copyright (C) 2020, Raspberry Pi Ltd
++ *
++ * dma_heaps.h - Helper class for dma-heap allocations.
++ */
++
++#include "libcamera/internal/dma_heaps.h"
++
++#include <array>
++#include <fcntl.h>
++#include <sys/ioctl.h>
++#include <unistd.h>
++
++#include <linux/dma-buf.h>
++#include <linux/dma-heap.h>
++
++#include <libcamera/base/log.h>
++
++/**
++ * \file dma_heaps.cpp
++ * \brief CMA dma-heap allocator
++ */
++
++/*
++ * /dev/dma_heap/linux,cma is the dma-heap allocator, which allows dmaheap-cma
++ * to only have to worry about importing.
++ *
++ * 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"
++};
++
++namespace libcamera {
++
++LOG_DEFINE_CATEGORY(DmaHeap)
++
++/**
++ * \class DmaHeap
++ * \brief Helper class for CMA dma-heap allocations
++ */
++
++/**
++ * \brief Construct a DmaHeap that owns a CMA dma-heap file descriptor
++ *
++ * Goes through the internal list of possible names of the CMA dma-heap devices
++ * until a CMA dma-heap device is successfully opened. If it fails to open any
++ * dma-heap device, an invalid DmaHeap object is constructed. A valid DmaHeap
++ * object owns a wrapped dma-heap file descriptor.
++ *
++ * Please check the new DmaHeap object with \ref DmaHeap::isValid before using it.
++ */
++DmaHeap::DmaHeap()
++{
++	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;
++		}
++
++		dmaHeapHandle_ = UniqueFD(ret);
++		break;
++	}
++
++	if (!dmaHeapHandle_.isValid())
++		LOG(DmaHeap, Error) << "Could not open any dmaHeap device";
++}
++
++/**
++ * \brief Destroy the DmaHeap instance
++ *
++ * Destroying a DmaHeap instance which owns a wrapped dma-heap file descriptor
++ * closes the descriptor automatically.
++ */
++DmaHeap::~DmaHeap() = default;
++
++/**
++ * \fn DmaHeap::isValid()
++ * \brief Check if the DmaHeap instance is valid
++ * \return True if the DmaHeap is valid, false otherwise
++ */
++
++/**
++ * \brief Allocate a dma-buf from the DmaHeap
++ * \param [in] name The name to set for the allocated buffer
++ * \param [in] size The size of the buffer to allocate
++ * \return The \ref UniqueFD of the allocated buffer
++ *
++ * Allocates a dma-buf with read/write access.
++ * If the allocation fails returns invalid UniqueFD.
++ */
++UniqueFD DmaHeap::alloc(const char *name, std::size_t size)
++{
++	int ret;
++
++	if (!name)
++		return {};
++
++	struct dma_heap_allocation_data alloc = {};
++
++	alloc.len = size;
++	alloc.fd_flags = O_CLOEXEC | O_RDWR;
++
++	ret = ::ioctl(dmaHeapHandle_.get(), DMA_HEAP_IOCTL_ALLOC, &alloc);
++	if (ret < 0) {
++		LOG(DmaHeap, Error) << "dmaHeap allocation failure for " << name;
++		return {};
++	}
++
++	UniqueFD allocFd(alloc.fd);
++	ret = ::ioctl(allocFd.get(), DMA_BUF_SET_NAME, name);
++	if (ret < 0) {
++		LOG(DmaHeap, Error) << "dmaHeap naming failure for " << name;
++		return {};
++	}
++
++	return allocFd;
++}
++
++} /* 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/dma_heaps.cpp b/src/libcamera/pipeline/rpi/vc4/dma_heaps.cpp
+deleted file mode 100644
+index 317b1fc1..00000000
+--- a/src/libcamera/pipeline/rpi/vc4/dma_heaps.cpp
++++ /dev/null
+@@ -1,90 +0,0 @@
+-/* SPDX-License-Identifier: LGPL-2.1-or-later */
+-/*
+- * Copyright (C) 2020, Raspberry Pi Ltd
+- *
+- * dma_heaps.h - Helper class for dma-heap allocations.
+- */
+-
+-#include "dma_heaps.h"
+-
+-#include <array>
+-#include <fcntl.h>
+-#include <linux/dma-buf.h>
+-#include <linux/dma-heap.h>
+-#include <sys/ioctl.h>
+-#include <unistd.h>
+-
+-#include <libcamera/base/log.h>
+-
+-/*
+- * /dev/dma-heap/linux,cma is the dma-heap allocator, which allows dmaheap-cma
+- * to only have to worry about importing.
+- *
+- * 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"
+-};
+-
+-namespace libcamera {
+-
+-LOG_DECLARE_CATEGORY(RPI)
+-
+-namespace RPi {
+-
+-DmaHeap::DmaHeap()
+-{
+-	for (const char *name : heapNames) {
+-		int ret = ::open(name, O_RDWR | O_CLOEXEC, 0);
+-		if (ret < 0) {
+-			ret = errno;
+-			LOG(RPI, Debug) << "Failed to open " << name << ": "
+-					<< strerror(ret);
+-			continue;
+-		}
+-
+-		dmaHeapHandle_ = UniqueFD(ret);
+-		break;
+-	}
+-
+-	if (!dmaHeapHandle_.isValid())
+-		LOG(RPI, Error) << "Could not open any dmaHeap device";
+-}
+-
+-DmaHeap::~DmaHeap() = default;
+-
+-UniqueFD DmaHeap::alloc(const char *name, std::size_t size)
+-{
+-	int ret;
+-
+-	if (!name)
+-		return {};
+-
+-	struct dma_heap_allocation_data alloc = {};
+-
+-	alloc.len = size;
+-	alloc.fd_flags = O_CLOEXEC | O_RDWR;
+-
+-	ret = ::ioctl(dmaHeapHandle_.get(), DMA_HEAP_IOCTL_ALLOC, &alloc);
+-	if (ret < 0) {
+-		LOG(RPI, Error) << "dmaHeap allocation failure for "
+-				<< name;
+-		return {};
+-	}
+-
+-	UniqueFD allocFd(alloc.fd);
+-	ret = ::ioctl(allocFd.get(), DMA_BUF_SET_NAME, name);
+-	if (ret < 0) {
+-		LOG(RPI, Error) << "dmaHeap naming failure for "
+-				<< name;
+-		return {};
+-	}
+-
+-	return allocFd;
+-}
+-
+-} /* namespace RPi */
+-
+-} /* namespace libcamera */
+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.2
+
diff --git a/users/flokli/ipu6-softisp/libcamera/0003-libcamera-dma_heaps-extend-DmaHeap-class-to-support-.patch b/users/flokli/ipu6-softisp/libcamera/0003-libcamera-dma_heaps-extend-DmaHeap-class-to-support-.patch
new file mode 100644
index 0000000000..6e5ef9445a
--- /dev/null
+++ b/users/flokli/ipu6-softisp/libcamera/0003-libcamera-dma_heaps-extend-DmaHeap-class-to-support-.patch
@@ -0,0 +1,169 @@
+From 5df9bc3b2a3d86bcc8504896cc87d7fcb5aea3a4 Mon Sep 17 00:00:00 2001
+From: Andrey Konovalov <andrey.konovalov@linaro.org>
+Date: Mon, 11 Mar 2024 15:15:07 +0100
+Subject: [PATCH 03/21] 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.
+
+Tested-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org> # sc8280xp Lenovo x13s
+Tested-by: Pavel Machek <pavel@ucw.cz>
+Reviewed-by: Milan Zamazal <mzamazal@redhat.com>
+Reviewed-by: Pavel Machek <pavel@ucw.cz>
+Signed-off-by: Andrey Konovalov <andrey.konovalov@linaro.org>
+Signed-off-by: Hans de Goede <hdegoede@redhat.com>
+---
+ include/libcamera/internal/dma_heaps.h | 12 ++++-
+ src/libcamera/dma_heaps.cpp            | 67 ++++++++++++++++++++------
+ 2 files changed, 63 insertions(+), 16 deletions(-)
+
+diff --git a/include/libcamera/internal/dma_heaps.h b/include/libcamera/internal/dma_heaps.h
+index cff8f140..80bf29e7 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 38ef175a..d0e33ce6 100644
+--- a/src/libcamera/dma_heaps.cpp
++++ b/src/libcamera/dma_heaps.cpp
+@@ -19,9 +19,11 @@
+ 
+ /**
+  * \file dma_heaps.cpp
+- * \brief CMA dma-heap allocator
++ * \brief dma-heap allocator
+  */
+ 
++namespace libcamera {
++
+ /*
+  * /dev/dma_heap/linux,cma is the dma-heap allocator, which allows dmaheap-cma
+  * to only have to worry about importing.
+@@ -29,42 +31,77 @@
+  * 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"
++
++/**
++ * \struct DmaHeapInfo
++ * \brief Tells what type of dma-heap the dma-heap represented by the device node name is
++ * \var DmaHeapInfo::flag
++ * \brief The type of the dma-heap
++ * \var DmaHeapInfo::name
++ * \brief The dma-heap's device node name
++ */
++struct DmaHeapInfo {
++	DmaHeap::DmaHeapFlag flag;
++	const char *name;
+ };
+ 
+-namespace libcamera {
++static constexpr std::array<DmaHeapInfo, 3> heapInfos = {
++	{ /* CMA heap names first */
++	  { DmaHeap::DmaHeapFlag::Cma, "/dev/dma_heap/linux,cma" },
++	  { DmaHeap::DmaHeapFlag::Cma, "/dev/dma_heap/reserved" },
++	  { DmaHeap::DmaHeapFlag::System, "/dev/dma_heap/system" } }
++};
+ 
+ LOG_DEFINE_CATEGORY(DmaHeap)
+ 
+ /**
+  * \class DmaHeap
+- * \brief Helper class for CMA dma-heap allocations
++ * \brief Helper class for dma-heap allocations
+  */
+ 
+ /**
+- * \brief Construct a DmaHeap that owns a CMA dma-heap file descriptor
++ * \enum DmaHeap::DmaHeapFlag
++ * \brief Type of the dma-heap
++ * \var DmaHeap::Cma
++ * \brief Allocate from a CMA dma-heap
++ * \var DmaHeap::System
++ * \brief Allocate from the system dma-heap
++ */
++
++/**
++ * \typedef DmaHeap::DmaHeapFlags
++ * \brief A bitwise combination of DmaHeap::DmaHeapFlag values
++ */
++
++/**
++ * \brief Construct a DmaHeap that owns a CMA or system dma-heap file descriptor
++ * \param [in] flags The type(s) of the dma-heap(s) to allocate from
+  *
+- * Goes through the internal list of possible names of the CMA dma-heap devices
+- * until a CMA dma-heap device is successfully opened. If it fails to open any
+- * dma-heap device, an invalid DmaHeap object is constructed. A valid DmaHeap
+- * object owns a wrapped dma-heap file descriptor.
++ * By default \a flags are set to DmaHeap::DmaHeapFlag::Cma. The constructor goes
++ * through the internal list of possible names of the CMA and system dma-heap devices
++ * until the dma-heap device of the requested type is successfully opened. If more
++ * than one dma-heap type is specified in flags the CMA heap is tried first. If it
++ * fails to open any dma-heap device an invalid DmaHeap object is constructed.
++ * A valid DmaHeap object owns a wrapped dma-heap file descriptor.
+  *
+  * Please check the new DmaHeap object with \ref DmaHeap::isValid before using it.
+  */
+-DmaHeap::DmaHeap()
++DmaHeap::DmaHeap(DmaHeapFlags flags)
+ {
+-	for (const char *name : heapNames) {
+-		int ret = ::open(name, O_RDWR | O_CLOEXEC, 0);
++	for (const auto &info : heapInfos) {
++		if (!(flags & info.flag))
++			continue;
++
++		int ret = ::open(info.name, O_RDWR | O_CLOEXEC, 0);
+ 		if (ret < 0) {
+ 			ret = errno;
+ 			LOG(DmaHeap, Debug)
+-				<< "Failed to open " << name << ": "
++				<< "Failed to open " << info.name << ": "
+ 				<< strerror(ret);
+ 			continue;
+ 		}
+ 
++		LOG(DmaHeap, Debug) << "Using " << info.name;
+ 		dmaHeapHandle_ = UniqueFD(ret);
+ 		break;
+ 	}
+-- 
+2.43.2
+
diff --git a/users/flokli/ipu6-softisp/libcamera/0004-libcamera-internal-Move-SharedMemObject-class-to-a-c.patch b/users/flokli/ipu6-softisp/libcamera/0004-libcamera-internal-Move-SharedMemObject-class-to-a-c.patch
new file mode 100644
index 0000000000..48f10aa47a
--- /dev/null
+++ b/users/flokli/ipu6-softisp/libcamera/0004-libcamera-internal-Move-SharedMemObject-class-to-a-c.patch
@@ -0,0 +1,69 @@
+From a6777760a2121f02808baecea504ac0e242f860b Mon Sep 17 00:00:00 2001
+From: Andrey Konovalov <andrey.konovalov@linaro.org>
+Date: Mon, 11 Mar 2024 15:15:08 +0100
+Subject: [PATCH 04/21] 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.
+
+Tested-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org> # sc8280xp Lenovo x13s
+Tested-by: Pavel Machek <pavel@ucw.cz>
+Reviewed-by: Pavel Machek <pavel@ucw.cz>
+Signed-off-by: Andrey Konovalov <andrey.konovalov@linaro.org>
+Signed-off-by: Hans de Goede <hdegoede@redhat.com>
+Reviewed-by: Milan Zamazal <mzamazal@redhat.com>
+---
+ include/libcamera/internal/meson.build                      | 1 +
+ .../libcamera/internal}/shared_mem_object.h                 | 6 +-----
+ 2 files changed, 2 insertions(+), 5 deletions(-)
+ rename {src/libcamera/pipeline/rpi/common => include/libcamera/internal}/shared_mem_object.h (97%)
+
+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 97%
+rename from src/libcamera/pipeline/rpi/common/shared_mem_object.h
+rename to include/libcamera/internal/shared_mem_object.h
+index aa56c220..98636b44 100644
+--- a/src/libcamera/pipeline/rpi/common/shared_mem_object.h
++++ b/include/libcamera/internal/shared_mem_object.h
+@@ -6,8 +6,8 @@
+  */
+ #pragma once
+ 
+-#include <cstddef>
+ #include <fcntl.h>
++#include <stddef.h>
+ #include <string>
+ #include <sys/mman.h>
+ #include <sys/stat.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.2
+
diff --git a/users/flokli/ipu6-softisp/libcamera/0005-libcamera-shared_mem_object-reorganize-the-code-and-.patch b/users/flokli/ipu6-softisp/libcamera/0005-libcamera-shared_mem_object-reorganize-the-code-and-.patch
new file mode 100644
index 0000000000..d2143febf7
--- /dev/null
+++ b/users/flokli/ipu6-softisp/libcamera/0005-libcamera-shared_mem_object-reorganize-the-code-and-.patch
@@ -0,0 +1,403 @@
+From f94af21adc1889706127d07c5425f44c9cec9a95 Mon Sep 17 00:00:00 2001
+From: Andrei Konovalov <andrey.konovalov.ynk@gmail.com>
+Date: Mon, 11 Mar 2024 15:15:09 +0100
+Subject: [PATCH 05/21] libcamera: shared_mem_object: reorganize the code and
+ document the SharedMemObject class
+
+Split the parts which doesn't otherwise depend on the type T or
+arguments Args out of the SharedMemObject class into a new
+SharedMem class.
+
+Doxygen documentation by Dennis Bonke and Andrei Konovalov.
+
+Reviewed-by: Pavel Machek <pavel@ucw.cz>
+Co-developed-by: Dennis Bonke <admin@dennisbonke.com>
+Signed-off-by: Dennis Bonke <admin@dennisbonke.com>
+Signed-off-by: Andrei Konovalov <andrey.konovalov.ynk@gmail.com>
+Signed-off-by: Hans de Goede <hdegoede@redhat.com>
+Reviewed-by: Milan Zamazal <mzamazal@redhat.com>
+---
+ .../libcamera/internal/shared_mem_object.h    | 101 ++++++----
+ src/libcamera/meson.build                     |   1 +
+ src/libcamera/shared_mem_object.cpp           | 190 ++++++++++++++++++
+ 3 files changed, 253 insertions(+), 39 deletions(-)
+ create mode 100644 src/libcamera/shared_mem_object.cpp
+
+diff --git a/include/libcamera/internal/shared_mem_object.h b/include/libcamera/internal/shared_mem_object.h
+index 98636b44..43b07c9d 100644
+--- a/include/libcamera/internal/shared_mem_object.h
++++ b/include/libcamera/internal/shared_mem_object.h
+@@ -6,12 +6,9 @@
+  */
+ #pragma once
+ 
+-#include <fcntl.h>
+ #include <stddef.h>
+ #include <string>
+ #include <sys/mman.h>
+-#include <sys/stat.h>
+-#include <unistd.h>
+ #include <utility>
+ 
+ #include <libcamera/base/class.h>
+@@ -19,58 +16,92 @@
+ 
+ namespace libcamera {
+ 
++class SharedMem
++{
++public:
++	SharedMem()
++		: mem_(nullptr)
++	{
++	}
++
++	SharedMem(const std::string &name, std::size_t size);
++
++	SharedMem(SharedMem &&rhs)
++	{
++		this->name_ = std::move(rhs.name_);
++		this->fd_ = std::move(rhs.fd_);
++		this->mem_ = rhs.mem_;
++		rhs.mem_ = nullptr;
++	}
++
++	virtual ~SharedMem()
++	{
++		if (mem_)
++			munmap(mem_, size_);
++	}
++
++	/* Make SharedMem non-copyable for now. */
++	LIBCAMERA_DISABLE_COPY(SharedMem)
++
++	SharedMem &operator=(SharedMem &&rhs)
++	{
++		this->name_ = std::move(rhs.name_);
++		this->fd_ = std::move(rhs.fd_);
++		this->mem_ = rhs.mem_;
++		rhs.mem_ = nullptr;
++		return *this;
++	}
++
++	const SharedFD &fd() const
++	{
++		return fd_;
++	}
++
++	void *mem() const
++	{
++		return mem_;
++	}
++
++private:
++	std::string name_;
++	SharedFD fd_;
++	size_t size_;
++protected:
++	void *mem_;
++};
++
+ template<class T>
+-class SharedMemObject
++class SharedMemObject : public SharedMem
+ {
+ public:
+ 	static constexpr std::size_t SIZE = sizeof(T);
+ 
+ 	SharedMemObject()
+-		: obj_(nullptr)
++		: SharedMem(), obj_(nullptr)
+ 	{
+ 	}
+ 
+ 	template<class... Args>
+ 	SharedMemObject(const std::string &name, Args &&...args)
+-		: name_(name), obj_(nullptr)
++		: SharedMem(name, SIZE), obj_(nullptr)
+ 	{
+-		void *mem;
+-		int ret;
+-
+-		ret = memfd_create(name_.c_str(), MFD_CLOEXEC);
+-		if (ret < 0)
+-			return;
+-
+-		fd_ = SharedFD(std::move(ret));
+-		if (!fd_.isValid())
+-			return;
+-
+-		ret = ftruncate(fd_.get(), SIZE);
+-		if (ret < 0)
++		if (mem_ == nullptr)
+ 			return;
+ 
+-		mem = mmap(nullptr, SIZE, PROT_READ | PROT_WRITE, MAP_SHARED,
+-			   fd_.get(), 0);
+-		if (mem == MAP_FAILED)
+-			return;
+-
+-		obj_ = new (mem) T(std::forward<Args>(args)...);
++		obj_ = new (mem_) T(std::forward<Args>(args)...);
+ 	}
+ 
+ 	SharedMemObject(SharedMemObject<T> &&rhs)
++		: SharedMem(std::move(rhs))
+ 	{
+-		this->name_ = std::move(rhs.name_);
+-		this->fd_ = std::move(rhs.fd_);
+ 		this->obj_ = rhs.obj_;
+ 		rhs.obj_ = nullptr;
+ 	}
+ 
+ 	~SharedMemObject()
+ 	{
+-		if (obj_) {
++		if (obj_)
+ 			obj_->~T();
+-			munmap(obj_, SIZE);
+-		}
+ 	}
+ 
+ 	/* Make SharedMemObject non-copyable for now. */
+@@ -78,8 +109,7 @@ public:
+ 
+ 	SharedMemObject<T> &operator=(SharedMemObject<T> &&rhs)
+ 	{
+-		this->name_ = std::move(rhs.name_);
+-		this->fd_ = std::move(rhs.fd_);
++		SharedMem::operator=(std::move(rhs));
+ 		this->obj_ = rhs.obj_;
+ 		rhs.obj_ = nullptr;
+ 		return *this;
+@@ -105,19 +135,12 @@ public:
+ 		return *obj_;
+ 	}
+ 
+-	const SharedFD &fd() const
+-	{
+-		return fd_;
+-	}
+-
+ 	explicit operator bool() const
+ 	{
+ 		return !!obj_;
+ 	}
+ 
+ private:
+-	std::string name_;
+-	SharedFD fd_;
+ 	T *obj_;
+ };
+ 
+diff --git a/src/libcamera/meson.build b/src/libcamera/meson.build
+index 3c5e43df..94a95ae3 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',
++    'shared_mem_object.cpp',
+     'source_paths.cpp',
+     'stream.cpp',
+     'sysfs.cpp',
+diff --git a/src/libcamera/shared_mem_object.cpp b/src/libcamera/shared_mem_object.cpp
+new file mode 100644
+index 00000000..44fe74c2
+--- /dev/null
++++ b/src/libcamera/shared_mem_object.cpp
+@@ -0,0 +1,190 @@
++/* SPDX-License-Identifier: LGPL-2.1-or-later */
++/*
++ * Copyright (C) 2023, Raspberry Pi Ltd
++ *
++ * shared_mem_object.cpp - Helper class for shared memory allocations
++ */
++
++#include "libcamera/internal/shared_mem_object.h"
++
++#include <sys/types.h>
++#include <unistd.h>
++
++/**
++ * \file shared_mem_object.cpp
++ * \brief Helper class for shared memory allocations
++ */
++
++namespace libcamera {
++
++/**
++ * \class SharedMem
++ * \brief Helper class for allocating shared memory
++ *
++ * Memory is allocated and exposed as a SharedFD for use across IPC boundaries.
++ *
++ * SharedMem allocates the shared memory of the given size and maps it.
++ * To check that the shared memory was allocated and mapped successfully, one
++ * needs to verify that the pointer to the shared memory returned by SharedMem::mem()
++ * is not nullptr.
++ *
++ * To access the shared memory from another process the SharedFD should be passed
++ * to that process, and then the shared memory should be mapped into that process
++ * address space by calling mmap().
++ *
++ * A single memfd is created for every SharedMem. If there is a need to allocate
++ * a large number of objects in shared memory, these objects should be grouped
++ * together and use the shared memory allocated by a single SharedMem object if
++ * possible. This will help to minimize the number of created memfd's.
++ */
++
++/**
++ * \fn SharedMem::SharedMem(const std::string &name, std::size_t size)
++ * \brief Constructor for the SharedMem
++ * \param[in] name Name of the SharedMem
++ * \param[in] size Size of the shared memory to allocate and map
++ */
++
++/**
++ * \fn SharedMem::SharedMem(SharedMem &&rhs)
++ * \brief Move constructor for SharedMem
++ * \param[in] rhs The object to move
++ */
++
++/**
++ * \fn SharedMem::~SharedMem()
++ * \brief SharedMem destructor
++ *
++ * Unmaps the allocated shared memory. Decrements the shared memory descriptor use
++ * count.
++ */
++
++/**
++ * \fn SharedMem &SharedMem::operator=(SharedMem &&rhs)
++ * \brief Move constructor for SharedMem
++ * \param[in] rhs The object to move
++ */
++
++/**
++ * \fn const SharedFD &SharedMem::fd() const
++ * \brief Gets the file descriptor for the underlying shared memory
++ * \return The file descriptor
++ */
++
++/**
++ * \fn void *SharedMem::mem() const
++ * \brief Gets the pointer to the underlying shared memory
++ * \return The pointer to the shared memory
++ */
++
++SharedMem::SharedMem(const std::string &name, std::size_t size)
++	: name_(name), size_(size), mem_(nullptr)
++{
++	int fd = memfd_create(name_.c_str(), MFD_CLOEXEC);
++	if (fd < 0)
++		return;
++
++	fd_ = SharedFD(std::move(fd));
++	if (!fd_.isValid())
++		return;
++
++	if (ftruncate(fd_.get(), size_) < 0)
++		return;
++
++	mem_ = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED,
++		    fd_.get(), 0);
++	if (mem_ == MAP_FAILED)
++		mem_ = nullptr;
++}
++
++/**
++ * \var SharedMem::mem_
++ * \brief Pointer to the shared memory allocated
++ */
++
++/**
++ * \class SharedMemObject
++ * \brief Helper class for allocating objects in shared memory
++ *
++ * Memory is allocated and exposed as a SharedFD for use across IPC boundaries.
++ *
++ * Given the type of the object to be created in shared memory and the arguments
++ * to pass to this object's constructor, SharedMemObject allocates the shared memory
++ * of the size of the object and constructs the object in this memory. To ensure
++ * that the SharedMemObject was created successfully, one needs to verify that the
++ * overloaded bool() operator returns true. The object created in the shared memory
++ * can be accessed using the SharedMemObject::operator*() indirection operator. Its
++ * members can be accessed with the SharedMemObject::operator->() member of pointer
++ * operator.
++ *
++ * To access the object from another process the SharedFD should be passed to that
++ * process, and the shared memory should be mapped by calling mmap().
++ *
++ * A single memfd is created for every SharedMemObject. If there is a need to allocate
++ * a large number of objects in shared memory, these objects should be grouped into a
++ * single large object to keep the number of created memfd's reasonably small.
++ */
++
++/**
++ * \var SharedMemObject::SIZE
++ * \brief The size of the object that is going to be stored here
++ */
++
++/**
++ * \fn SharedMemObject< T >::SharedMemObject(const std::string &name, Args &&...args)
++ * \brief Constructor for the SharedMemObject
++ * \param[in] name Name of the SharedMemObject
++ * \param[in] args Args to pass to the constructor of the object in shared memory
++ */
++
++/**
++ * \fn SharedMemObject::SharedMemObject(SharedMemObject<T> &&rhs)
++ * \brief Move constructor for SharedMemObject
++ * \param[in] rhs The object to move
++ */
++
++/**
++ * \fn SharedMemObject::~SharedMemObject()
++ * \brief SharedMemObject destructor
++ *
++ * Destroys the object created in the shared memory and then unmaps the shared memory.
++ * Decrements the shared memory descriptor use count.
++ */
++
++/**
++ * \fn SharedMemObject::operator=(SharedMemObject<T> &&rhs)
++ * \brief Operator= for SharedMemObject
++ * \param[in] rhs The SharedMemObject object to take the data from
++ */
++
++/**
++ * \fn SharedMemObject::operator->()
++ * \brief Operator-> for SharedMemObject
++ * \return The pointer to the object
++ */
++
++/**
++ * \fn const T *SharedMemObject::operator->() const
++ * \brief Operator-> for SharedMemObject
++ * \return The pointer to the const object
++ */
++
++/**
++ * \fn SharedMemObject::operator*()
++ * \brief Operator* for SharedMemObject
++ * \return The reference to the object
++ */
++
++/**
++ * \fn const T &SharedMemObject::operator*() const
++ * \brief Operator* for SharedMemObject
++ * \return Const reference to the object
++ */
++
++/**
++ * \fn SharedMemObject::operator bool()
++ * \brief Operator bool() for SharedMemObject
++ * \return True if the object was created OK in the shared memory, false otherwise
++ */
++
++} // namespace libcamera
+-- 
+2.43.2
+
diff --git a/users/flokli/ipu6-softisp/libcamera/0006-libcamera-software_isp-Add-SwStatsCpu-class.patch b/users/flokli/ipu6-softisp/libcamera/0006-libcamera-software_isp-Add-SwStatsCpu-class.patch
new file mode 100644
index 0000000000..9f80b69f16
--- /dev/null
+++ b/users/flokli/ipu6-softisp/libcamera/0006-libcamera-software_isp-Add-SwStatsCpu-class.patch
@@ -0,0 +1,523 @@
+From 4259b01930333c6666a185d923e6e68ec915a4fd Mon Sep 17 00:00:00 2001
+From: Hans de Goede <hdegoede@redhat.com>
+Date: Mon, 11 Mar 2024 15:15:10 +0100
+Subject: [PATCH 06/21] libcamera: software_isp: Add SwStatsCpu class
+
+Add a CPU based SwStats implementation for SoftwareISP / SoftIPA use.
+
+This implementation offers 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 implementation also allows specifying a window over which to gather
+statistics instead of processing the whole frame.
+
+Doxygen documentation by Dennis Bonke.
+
+Tested-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org> # sc8280xp Lenovo x13s
+Tested-by: Pavel Machek <pavel@ucw.cz>
+Reviewed-by: Pavel Machek <pavel@ucw.cz>
+Reviewed-by: Milan Zamazal <mzamazal@redhat.com>
+Co-developed-by: Andrey Konovalov <andrey.konovalov@linaro.org>
+Signed-off-by: Andrey Konovalov <andrey.konovalov@linaro.org>
+Co-developed-by: Pavel Machek <pavel@ucw.cz>
+Signed-off-by: Pavel Machek <pavel@ucw.cz>
+Co-developed-by: Dennis Bonke <admin@dennisbonke.com>
+Signed-off-by: Dennis Bonke <admin@dennisbonke.com>
+Co-developed-by: Marttico <g.martti@gmail.com>
+Signed-off-by: Marttico <g.martti@gmail.com>
+Co-developed-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>
+---
+ include/libcamera/internal/meson.build        |   1 +
+ .../internal/software_isp/meson.build         |   5 +
+ .../internal/software_isp/swisp_stats.h       |  38 ++++
+ src/libcamera/meson.build                     |   1 +
+ src/libcamera/software_isp/meson.build        |  12 +
+ src/libcamera/software_isp/swstats_cpu.cpp    | 208 ++++++++++++++++++
+ src/libcamera/software_isp/swstats_cpu.h      | 159 +++++++++++++
+ 7 files changed, 424 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 src/libcamera/software_isp/meson.build
+ create mode 100644 src/libcamera/software_isp/swstats_cpu.cpp
+ create mode 100644 src/libcamera/software_isp/swstats_cpu.h
+
+diff --git a/include/libcamera/internal/meson.build b/include/libcamera/internal/meson.build
+index 5807dfd9..160fdc37 100644
+--- a/include/libcamera/internal/meson.build
++++ b/include/libcamera/internal/meson.build
+@@ -50,3 +50,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..66c9c3fb
+--- /dev/null
++++ b/include/libcamera/internal/software_isp/meson.build
+@@ -0,0 +1,5 @@
++# SPDX-License-Identifier: CC0-1.0
++
++libcamera_internal_headers += files([
++    'swisp_stats.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..afe42c9a
+--- /dev/null
++++ b/include/libcamera/internal/software_isp/swisp_stats.h
+@@ -0,0 +1,38 @@
++/* 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 Number of bins in the yHistogram.
++	 */
++	static constexpr unsigned int kYHistogramSize = 16;
++	/**
++	 * \brief A histogram of luminance values.
++	 */
++	std::array<unsigned int, kYHistogramSize> yHistogram;
++};
++
++} /* namespace libcamera */
+diff --git a/src/libcamera/meson.build b/src/libcamera/meson.build
+index 94a95ae3..91e4cc60 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..fcfff74a
+--- /dev/null
++++ b/src/libcamera/software_isp/meson.build
+@@ -0,0 +1,12 @@
++# SPDX-License-Identifier: CC0-1.0
++
++softisp_enabled = pipelines.contains('simple')
++summary({'SoftISP support' : softisp_enabled}, section : 'Configuration')
++
++if not (softisp_enabled)
++    subdir_done()
++endif
++
++libcamera_sources += files([
++    '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..448d0e4c
+--- /dev/null
++++ b/src/libcamera/software_isp/swstats_cpu.cpp
+@@ -0,0 +1,208 @@
++/* 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 "swstats_cpu.h"
++
++#include <libcamera/base/log.h>
++
++#include <libcamera/stream.h>
++
++#include "libcamera/internal/bayer_format.h"
++
++namespace libcamera {
++
++/**
++ * \class SwStatsCpu
++ * \brief Class for gathering statistics on the CPU
++ *
++ * CPU based software ISP statistics implementation.
++ *
++ * This class offers a configure function + functions to gather statistics
++ * on a line by line basis. This allows CPU based software debayering to
++ * interlace debayering and statistics gathering on a line by line basis
++ * while the input data is still hot in the cache.
++ *
++ * It is also possible to specify a window over which to gather
++ * statistics instead of processing the whole frame.
++ */
++
++LOG_DEFINE_CATEGORY(SwStatsCpu)
++
++SwStatsCpu::SwStatsCpu()
++{
++	sharedStats_ = SharedMemObject<SwIspStats>("softIsp_stats");
++	if (!sharedStats_.fd().isValid())
++		LOG(SwStatsCpu, Error)
++			<< "Failed to create shared memory for statistics";
++}
++
++static const unsigned int kRedYMul = 77; /* 0.299 * 256 */
++static const unsigned int kGreenYMul = 150; /* 0.587 * 256 */
++static const unsigned int kBlueYMul = 29; /* 0.114 * 256 */
++
++#define SWSTATS_START_LINE_STATS(pixel_t) \
++	pixel_t r, g, g2, b;              \
++	unsigned int yVal;                \
++                                          \
++	unsigned int sumR = 0;            \
++	unsigned int sumG = 0;            \
++	unsigned int sumB = 0;
++
++#define SWSTATS_ACCUMULATE_LINE_STATS(div) \
++	sumR += r;                         \
++	sumG += g;                         \
++	sumB += b;                         \
++                                           \
++	yVal = r * kRedYMul;               \
++	yVal += g * kGreenYMul;            \
++	yVal += b * kBlueYMul;             \
++	stats_.yHistogram[yVal * SwIspStats::kYHistogramSize / (256 * 256 * (div))]++;
++
++#define SWSTATS_FINISH_LINE_STATS() \
++	stats_.sumR_ += sumR;       \
++	stats_.sumG_ += sumG;       \
++	stats_.sumB_ += sumB;
++
++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;
++	const int widthInBytes = window_.width * 5 / 4;
++
++	if (swapLines_)
++		std::swap(src0, src1);
++
++	SWSTATS_START_LINE_STATS(uint8_t)
++
++	/* x += 5 sample every other 2x2 block */
++	for (int x = 0; x < widthInBytes; x += 5) {
++		/* BGGR */
++		b = src0[x];
++		g = src0[x + 1];
++		g2 = src1[x];
++		r = src1[x + 1];
++		g = (g + g2) / 2;
++		/* Data is already 8 bits, divide by 1 */
++		SWSTATS_ACCUMULATE_LINE_STATS(1)
++	}
++
++	SWSTATS_FINISH_LINE_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;
++	const int widthInBytes = window_.width * 5 / 4;
++
++	if (swapLines_)
++		std::swap(src0, src1);
++
++	SWSTATS_START_LINE_STATS(uint8_t)
++
++	/* x += 5 sample every other 2x2 block */
++	for (int x = 0; x < widthInBytes; x += 5) {
++		/* GBRG */
++		g = src0[x];
++		b = src0[x + 1];
++		r = src1[x];
++		g2 = src1[x + 1];
++		g = (g + g2) / 2;
++		/* Data is already 8 bits, divide by 1 */
++		SWSTATS_ACCUMULATE_LINE_STATS(1)
++	}
++
++	SWSTATS_FINISH_LINE_STATS()
++}
++
++/**
++ * \brief Reset state to start statistics gathering for a new frame.
++ *
++ * This may only be called after a successful setWindow() call.
++ */
++void SwStatsCpu::startFrame(void)
++{
++	stats_.sumR_ = 0;
++	stats_.sumB_ = 0;
++	stats_.sumG_ = 0;
++	stats_.yHistogram.fill(0);
++}
++
++/**
++ * \brief Finish statistics calculation for the current frame.
++ *
++ * This may only be called after a successful setWindow() call.
++ */
++void SwStatsCpu::finishFrame(void)
++{
++	*sharedStats_ = stats_;
++	statsReady.emit(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
++ */
++int SwStatsCpu::configure(const StreamConfiguration &inputCfg)
++{
++	BayerFormat bayerFormat =
++		BayerFormat::fromPixelFormat(inputCfg.pixelFormat);
++
++	if (bayerFormat.bitDepth == 10 &&
++	    bayerFormat.packing == BayerFormat::Packing::CSI2) {
++		patternSize_.height = 2;
++		patternSize_.width = 4; /* 5 bytes per *4* pixels */
++		/* Skip every 3th and 4th line, sample every other 2x2 block */
++		ySkipMask_ = 0x02;
++		xShift_ = 0;
++
++		switch (bayerFormat.order) {
++		case BayerFormat::BGGR:
++		case BayerFormat::GRBG:
++			stats0_ = &SwStatsCpu::statsBGGR10PLine0;
++			swapLines_ = bayerFormat.order == BayerFormat::GRBG;
++			return 0;
++		case BayerFormat::GBRG:
++		case BayerFormat::RGGB:
++			stats0_ = &SwStatsCpu::statsGBRG10PLine0;
++			swapLines_ = bayerFormat.order == BayerFormat::RGGB;
++			return 0;
++		default:
++			break;
++		}
++	}
++
++	LOG(SwStatsCpu, Info)
++		<< "Unsupported input format " << inputCfg.pixelFormat.toString();
++	return -EINVAL;
++}
++
++/**
++ * \brief Specify window coordinates over which to gather statistics.
++ * \param[in] window The window object.
++ */
++void SwStatsCpu::setWindow(Rectangle window)
++{
++	window_ = window;
++
++	window_.x &= ~(patternSize_.width - 1);
++	window_.x += xShift_;
++	window_.y &= ~(patternSize_.height - 1);
++
++	/* width_ - xShift_ to make sure the window fits */
++	window_.width -= xShift_;
++	window_.width &= ~(patternSize_.width - 1);
++	window_.height &= ~(patternSize_.height - 1);
++}
++
++} /* namespace libcamera */
+diff --git a/src/libcamera/software_isp/swstats_cpu.h b/src/libcamera/software_isp/swstats_cpu.h
+new file mode 100644
+index 00000000..0ac9ae71
+--- /dev/null
++++ b/src/libcamera/software_isp/swstats_cpu.h
+@@ -0,0 +1,159 @@
++/* 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 <stdint.h>
++
++#include <libcamera/base/signal.h>
++
++#include <libcamera/geometry.h>
++
++#include "libcamera/internal/shared_mem_object.h"
++#include "libcamera/internal/software_isp/swisp_stats.h"
++
++namespace libcamera {
++
++class PixelFormat;
++struct StreamConfiguration;
++
++class SwStatsCpu
++{
++public:
++	SwStatsCpu();
++	~SwStatsCpu() = default;
++
++	/**
++	 * \brief Gets whether the statistics object is valid.
++	 *
++	 * \return true if it's valid, false otherwise
++	 */
++	bool isValid() const { return sharedStats_.fd().isValid(); }
++
++	/**
++	 * \brief Get the file descriptor for the statistics.
++	 *
++	 * \return the file descriptor
++	 */
++	const SharedFD &getStatsFD() { return sharedStats_.fd(); }
++
++	/**
++	 * \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_; }
++
++	int configure(const StreamConfiguration &inputCfg);
++	void setWindow(Rectangle window);
++	void startFrame();
++	void finishFrame();
++
++	/**
++	 * \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 & ySkipMask_) || 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 & ySkipMask_) || y < (unsigned int)window_.y ||
++		    y >= (window_.y + window_.height))
++			return;
++
++		(this->*stats2_)(src);
++	}
++
++	/**
++	 * \brief Signals that the statistics are ready.
++	 *
++	 * The int parameter isn't actually used.
++	 */
++	Signal<int> statsReady;
++
++private:
++	/**
++	 * \brief Called when there is data to get statistics from.
++	 * \param[in] src The input data
++	 *
++	 * These functions take an array of (patternSize_.height + 1) src
++	 * pointers each pointing to a line in the source image. The middle
++	 * element of the array will point to the actual line being processed.
++	 * Earlier element(s) will point to the previous line(s) and later
++	 * element(s) to the next line(s).
++	 *
++	 * See the documentation of DebayerCpu::debayerFn for more details.
++	 */
++	using statsProcessFn = void (SwStatsCpu::*)(const uint8_t *src[]);
++
++	void statsBGGR10PLine0(const uint8_t *src[]);
++	void statsGBRG10PLine0(const uint8_t *src[]);
++
++	/* Variables set by configure(), used every line */
++	statsProcessFn stats0_;
++	statsProcessFn stats2_;
++	bool swapLines_;
++
++	/**
++	 * \brief Skip lines where this bitmask is set in y.
++	 */
++	unsigned int ySkipMask_;
++
++	/**
++	 * \brief Statistics window, set by setWindow(), used ever line.
++	 */
++	Rectangle window_;
++
++	/**
++	 * \brief The size of the bayer pattern.
++	 *
++	 * Valid sizes are: 2x2, 4x2 or 4x4.
++	 */
++	Size patternSize_;
++
++	/**
++	 * \brief The offset of x, applied to window_.x for bayer variants.
++	 *
++	 * This can either be 0 or 1.
++	 */
++	unsigned int xShift_;
++
++	SharedMemObject<SwIspStats> sharedStats_;
++	SwIspStats stats_;
++};
++
++} /* namespace libcamera */
+-- 
+2.43.2
+
diff --git a/users/flokli/ipu6-softisp/libcamera/0007-libcamera-software_isp-Add-Debayer-base-class.patch b/users/flokli/ipu6-softisp/libcamera/0007-libcamera-software_isp-Add-Debayer-base-class.patch
new file mode 100644
index 0000000000..7c71709896
--- /dev/null
+++ b/users/flokli/ipu6-softisp/libcamera/0007-libcamera-software_isp-Add-Debayer-base-class.patch
@@ -0,0 +1,255 @@
+From 25e6893e46bd2174f6913eea79817988d9280706 Mon Sep 17 00:00:00 2001
+From: Hans de Goede <hdegoede@redhat.com>
+Date: Mon, 11 Mar 2024 15:15:11 +0100
+Subject: [PATCH 07/21] 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.
+
+Tested-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org> # sc8280xp Lenovo x13s
+Tested-by: Pavel Machek <pavel@ucw.cz>
+Reviewed-by: Pavel Machek <pavel@ucw.cz>
+Reviewed-by: Milan Zamazal <mzamazal@redhat.com>
+Co-developed-by: Dennis Bonke <admin@dennisbonke.com>
+Signed-off-by: Dennis Bonke <admin@dennisbonke.com>
+Co-developed-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>
+---
+ .../internal/software_isp/debayer_params.h    |  48 ++++++++
+ .../internal/software_isp/meson.build         |   1 +
+ src/libcamera/software_isp/debayer.cpp        |  29 +++++
+ src/libcamera/software_isp/debayer.h          | 104 ++++++++++++++++++
+ src/libcamera/software_isp/meson.build        |   1 +
+ 5 files changed, 183 insertions(+)
+ create mode 100644 include/libcamera/internal/software_isp/debayer_params.h
+ create mode 100644 src/libcamera/software_isp/debayer.cpp
+ create mode 100644 src/libcamera/software_isp/debayer.h
+
+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..98965fa1
+--- /dev/null
++++ b/include/libcamera/internal/software_isp/debayer_params.h
+@@ -0,0 +1,48 @@
++/* SPDX-License-Identifier: LGPL-2.1-or-later */
++/*
++ * Copyright (C) 2023, Red Hat Inc.
++ *
++ * Authors:
++ * Hans de Goede <hdegoede@redhat.com>
++ *
++ * debayer_params.h - DebayerParams header
++ */
++
++#pragma once
++
++namespace libcamera {
++
++/**
++ * \brief Struct to hold the debayer parameters.
++ */
++struct DebayerParams {
++	/**
++	 * \brief const value for 1.0 gain
++	 */
++	static constexpr unsigned int kGain10 = 256;
++
++	/**
++	 * \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 66c9c3fb..a620e16d 100644
+--- a/include/libcamera/internal/software_isp/meson.build
++++ b/include/libcamera/internal/software_isp/meson.build
+@@ -1,5 +1,6 @@
+ # SPDX-License-Identifier: CC0-1.0
+ 
+ libcamera_internal_headers += files([
++    'debayer_params.h',
+     'swisp_stats.h',
+ ])
+diff --git a/src/libcamera/software_isp/debayer.cpp b/src/libcamera/software_isp/debayer.cpp
+new file mode 100644
+index 00000000..64f0b5a0
+--- /dev/null
++++ b/src/libcamera/software_isp/debayer.cpp
+@@ -0,0 +1,29 @@
++/* 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 "debayer.h"
++
++namespace libcamera {
++
++/**
++ * \class Debayer
++ * \brief Base debayering class
++ *
++ * Base class that provides functions for setting up the debayering process.
++ */
++
++LOG_DEFINE_CATEGORY(Debayer)
++
++Debayer::~Debayer()
++{
++}
++
++} /* namespace libcamera */
+diff --git a/src/libcamera/software_isp/debayer.h b/src/libcamera/software_isp/debayer.h
+new file mode 100644
+index 00000000..8880ff99
+--- /dev/null
++++ b/src/libcamera/software_isp/debayer.h
+@@ -0,0 +1,104 @@
++/* 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
++{
++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.
++	 *
++	 * Valid sizes are: 2x2, 4x2 or 4x4.
++	 *
++	 * \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.
++	 */
++	virtual SizeRange sizes(PixelFormat inputFormat, const Size &inputSize) = 0;
++
++	/**
++	 * \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/src/libcamera/software_isp/meson.build b/src/libcamera/software_isp/meson.build
+index fcfff74a..62095f61 100644
+--- a/src/libcamera/software_isp/meson.build
++++ b/src/libcamera/software_isp/meson.build
+@@ -8,5 +8,6 @@ if not (softisp_enabled)
+ endif
+ 
+ libcamera_sources += files([
++    'debayer.cpp',
+     'swstats_cpu.cpp',
+ ])
+-- 
+2.43.2
+
diff --git a/users/flokli/ipu6-softisp/libcamera/0008-libcamera-software_isp-Add-DebayerCpu-class.patch b/users/flokli/ipu6-softisp/libcamera/0008-libcamera-software_isp-Add-DebayerCpu-class.patch
new file mode 100644
index 0000000000..f549769f2f
--- /dev/null
+++ b/users/flokli/ipu6-softisp/libcamera/0008-libcamera-software_isp-Add-DebayerCpu-class.patch
@@ -0,0 +1,825 @@
+From 5f57a52ea1054cac73344d83ff605cba0df0d279 Mon Sep 17 00:00:00 2001
+From: Hans de Goede <hdegoede@redhat.com>
+Date: Mon, 11 Mar 2024 15:15:12 +0100
+Subject: [PATCH 08/21] 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.
+
+Tested-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org> # sc8280xp Lenovo x13s
+Tested-by: Pavel Machek <pavel@ucw.cz>
+Reviewed-by: Pavel Machek <pavel@ucw.cz>
+Co-developed-by: Dennis Bonke <admin@dennisbonke.com>
+Signed-off-by: Dennis Bonke <admin@dennisbonke.com>
+Co-developed-by: Andrey Konovalov <andrey.konovalov@linaro.org>
+Signed-off-by: Andrey Konovalov <andrey.konovalov@linaro.org>
+Co-developed-by: Pavel Machek <pavel@ucw.cz>
+Signed-off-by: Pavel Machek <pavel@ucw.cz>
+Signed-off-by: Hans de Goede <hdegoede@redhat.com>
+Reviewed-by: Milan Zamazal <mzamazal@redhat.com>
+---
+ src/libcamera/software_isp/debayer_cpu.cpp | 626 +++++++++++++++++++++
+ src/libcamera/software_isp/debayer_cpu.h   | 143 +++++
+ src/libcamera/software_isp/meson.build     |   1 +
+ 3 files changed, 770 insertions(+)
+ create mode 100644 src/libcamera/software_isp/debayer_cpu.cpp
+ create mode 100644 src/libcamera/software_isp/debayer_cpu.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..f932362c
+--- /dev/null
++++ b/src/libcamera/software_isp/debayer_cpu.cpp
+@@ -0,0 +1,626 @@
++/* 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 "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 {
++
++/**
++ * \class DebayerCpu
++ * \brief Class for debayering on the CPU
++ *
++ * Implementation for CPU based debayering
++ */
++
++/**
++ * \brief Constructs a DebayerCpu object.
++ * \param[in] stats Pointer to the stats object to use.
++ */
++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 (unsigned int i = 0; i < kGammaLookupSize; i++)
++		gamma_[i] = i / (kGammaLookupSize / kRGBLookupSize);
++
++	for (unsigned int i = 0; i < kMaxLineBuffers; i++)
++		lineBuffers_[i] = nullptr;
++}
++
++DebayerCpu::~DebayerCpu()
++{
++	for (unsigned int i = 0; i < kMaxLineBuffers; 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.
++	 */
++	for (int x = 0; x < width_in_bytes;) {
++		/* First pixel */
++		BGGR_BGR888(2, 1, 1)
++		/* Second pixel BGGR -> GBRG */
++		GBRG_BGR888(1, 1, 1)
++		/* Same thing for third and fourth pixels */
++		BGGR_BGR888(1, 1, 1)
++		GBRG_BGR888(1, 2, 1)
++		/* Skip 5th src byte with 4 x 2 least-significant-bits */
++		x++;
++	}
++}
++
++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;) {
++		/* First pixel */
++		GRBG_BGR888(2, 1, 1)
++		/* Second pixel GRBG -> RGGB */
++		RGGB_BGR888(1, 1, 1)
++		/* Same thing for third and fourth pixels */
++		GRBG_BGR888(1, 1, 1)
++		RGGB_BGR888(1, 2, 1)
++		/* Skip 5th src byte with 4 x 2 least-significant-bits */
++		x++;
++	}
++}
++
++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;) {
++		/* 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)
++		/* Skip 5th src byte with 4 x 2 least-significant-bits */
++		x++;
++	}
++}
++
++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;) {
++		/* 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)
++		/* Skip 5th src byte with 4 x 2 least-significant-bits */
++		x++;
++	}
++}
++
++static bool isStandardBayerOrder(BayerFormat::Order order)
++{
++	return order == BayerFormat::BGGR || order == BayerFormat::GBRG ||
++	       order == BayerFormat::GRBG || order == BayerFormat::RGGB;
++}
++
++/*
++ * Setup the Debayer object according to the passed in parameters.
++ * Return 0 on success, a negative errno value on failure
++ * (unsupported parameters).
++ */
++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()));
++
++	/* pad with patternSize.Width on both left and right side */
++	lineBufferPadding_ = inputConfig_.patternSize.width * inputConfig_.bpp / 8;
++	lineBufferLength_ = window_.width * inputConfig_.bpp / 8 +
++			    2 * lineBufferPadding_;
++	for (unsigned int i = 0;
++	     i < (inputConfig_.patternSize.height + 1) && enableInputMemcpy_;
++	     i++) {
++		free(lineBuffers_[i]);
++		lineBuffers_[i] = (uint8_t *)malloc(lineBufferLength_);
++		if (!lineBuffers_[i])
++			return -ENOMEM;
++	}
++
++	measuredFrames_ = 0;
++	frameProcessTime_ = 0;
++
++	return 0;
++}
++
++/*
++ * Get width and height at which the bayer-pattern repeats.
++ * Return pattern-size or an empty Size for an unsupported inputFormat.
++ */
++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::setupInputMemcpy(const uint8_t *linePointers[])
++{
++	const unsigned int patternHeight = inputConfig_.patternSize.height;
++
++	if (!enableInputMemcpy_)
++		return;
++
++	for (unsigned int i = 0; i < patternHeight; i++) {
++		memcpy(lineBuffers_[i], linePointers[i + 1] - lineBufferPadding_,
++		       lineBufferLength_);
++		linePointers[i + 1] = lineBuffers_[i] + lineBufferPadding_;
++	}
++
++	/* Point lineBufferIndex_ to first unused lineBuffer */
++	lineBufferIndex_ = patternHeight;
++}
++
++void DebayerCpu::shiftLinePointers(const uint8_t *linePointers[], const uint8_t *src)
++{
++	const unsigned int patternHeight = inputConfig_.patternSize.height;
++
++	for (unsigned int i = 0; i < patternHeight; i++)
++		linePointers[i] = linePointers[i + 1];
++
++	linePointers[patternHeight] = src +
++				      (patternHeight / 2) * (int)inputConfig_.stride;
++}
++
++void DebayerCpu::memcpyNextLine(const uint8_t *linePointers[])
++{
++	const unsigned int patternHeight = inputConfig_.patternSize.height;
++
++	if (!enableInputMemcpy_)
++		return;
++
++	memcpy(lineBuffers_[lineBufferIndex_], linePointers[patternHeight] - lineBufferPadding_,
++	       lineBufferLength_);
++	linePointers[patternHeight] = lineBuffers_[lineBufferIndex_] + lineBufferPadding_;
++
++	lineBufferIndex_ = (lineBufferIndex_ + 1) % (patternHeight + 1);
++}
++
++void DebayerCpu::process2(const uint8_t *src, uint8_t *dst)
++{
++	unsigned int y_end = window_.y + window_.height;
++	/* Holds [0] previous- [1] current- [2] next-line */
++	const uint8_t *linePointers[3];
++
++	/* Adjust src to top left corner of the window */
++	src += window_.y * inputConfig_.stride + window_.x * inputConfig_.bpp / 8;
++
++	/* [x] becomes [x - 1] after initial shiftLinePointers() call */
++	if (window_.y) {
++		linePointers[1] = src - inputConfig_.stride; /* previous-line */
++		linePointers[2] = src;
++	} else {
++		/* window_.y == 0, use the next line as prev line */
++		linePointers[1] = src + inputConfig_.stride;
++		linePointers[2] = src;
++		/* Last 2 lines also need special handling */
++		y_end -= 2;
++	}
++
++	setupInputMemcpy(linePointers);
++
++	for (unsigned int y = window_.y; y < y_end; y += 2) {
++		shiftLinePointers(linePointers, src);
++		memcpyNextLine(linePointers);
++		stats_->processLine0(y, linePointers);
++		(this->*debayer0_)(dst, linePointers);
++		src += inputConfig_.stride;
++		dst += outputConfig_.stride;
++
++		shiftLinePointers(linePointers, src);
++		memcpyNextLine(linePointers);
++		(this->*debayer1_)(dst, linePointers);
++		src += inputConfig_.stride;
++		dst += outputConfig_.stride;
++	}
++
++	if (window_.y == 0) {
++		shiftLinePointers(linePointers, src);
++		memcpyNextLine(linePointers);
++		stats_->processLine0(y_end, linePointers);
++		(this->*debayer0_)(dst, linePointers);
++		src += inputConfig_.stride;
++		dst += outputConfig_.stride;
++
++		shiftLinePointers(linePointers, src);
++		/* next line may point outside of src, use prev. */
++		linePointers[2] = linePointers[0];
++		(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;
++	/*
++	 * This holds pointers to [0] 2-lines-up [1] 1-line-up [2] current-line
++	 * [3] 1-line-down [4] 2-lines-down.
++	 */
++	const uint8_t *linePointers[5];
++
++	/* Adjust src to top left corner of the window */
++	src += window_.y * inputConfig_.stride + window_.x * inputConfig_.bpp / 8;
++
++	/* [x] becomes [x - 1] after initial shiftLinePointers() call */
++	linePointers[1] = src - 2 * inputConfig_.stride;
++	linePointers[2] = src - inputConfig_.stride;
++	linePointers[3] = src;
++	linePointers[4] = src + inputConfig_.stride;
++
++	setupInputMemcpy(linePointers);
++
++	for (unsigned int y = window_.y; y < y_end; y += 4) {
++		shiftLinePointers(linePointers, src);
++		memcpyNextLine(linePointers);
++		stats_->processLine0(y, linePointers);
++		(this->*debayer0_)(dst, linePointers);
++		src += inputConfig_.stride;
++		dst += outputConfig_.stride;
++
++		shiftLinePointers(linePointers, src);
++		memcpyNextLine(linePointers);
++		(this->*debayer1_)(dst, linePointers);
++		src += inputConfig_.stride;
++		dst += outputConfig_.stride;
++
++		shiftLinePointers(linePointers, src);
++		memcpyNextLine(linePointers);
++		stats_->processLine2(y, linePointers);
++		(this->*debayer2_)(dst, linePointers);
++		src += inputConfig_.stride;
++		dst += outputConfig_.stride;
++
++		shiftLinePointers(linePointers, src);
++		memcpyNextLine(linePointers);
++		(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::kLastFrameToMeasure) {
++		frameStartTime = {};
++		clock_gettime(CLOCK_MONOTONIC_RAW, &frameStartTime);
++	}
++
++	/* Apply DebayerParams */
++	if (params.gamma != gamma_correction_) {
++		for (unsigned int i = 0; i < kGammaLookupSize; i++)
++			gamma_[i] = UINT8_MAX * powf(i / (kGammaLookupSize - 1.0), params.gamma);
++
++		gamma_correction_ = params.gamma;
++	}
++
++	for (unsigned int i = 0; i < kRGBLookupSize; i++) {
++		constexpr unsigned int div =
++			kRGBLookupSize * DebayerParams::kGain10 / kGammaLookupSize;
++		unsigned int idx;
++
++		/* Apply gamma after gain! */
++		idx = std::min({ i * params.gainR / div, (kGammaLookupSize - 1) });
++		red_[i] = gamma_[idx];
++
++		idx = std::min({ i * params.gainG / div, (kGammaLookupSize - 1) });
++		green_[i] = gamma_[idx];
++
++		idx = std::min({ i * params.gainB / div, (kGammaLookupSize - 1) });
++		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::kLastFrameToMeasure &&
++	    ++measuredFrames_ > DebayerCpu::kFramesToSkip) {
++		timespec frameEndTime = {};
++		clock_gettime(CLOCK_MONOTONIC_RAW, &frameEndTime);
++		frameProcessTime_ += timeDiff(frameEndTime, frameStartTime);
++		if (measuredFrames_ == DebayerCpu::kLastFrameToMeasure) {
++			const unsigned int measuredFrames = DebayerCpu::kLastFrameToMeasure -
++							    DebayerCpu::kFramesToSkip;
++			LOG(Debayer, Info)
++				<< "Processed " << measuredFrames
++				<< " frames in " << frameProcessTime_ / 1000 << "us, "
++				<< frameProcessTime_ / (1000 * measuredFrames)
++				<< " us/frame";
++		}
++	}
++
++	stats_->finishFrame();
++	outputBufferReady.emit(output);
++	inputBufferReady.emit(input);
++}
++
++SizeRange DebayerCpu::sizes(PixelFormat inputFormat, const Size &inputSize)
++{
++	Size pattern_size = patternSize(inputFormat);
++	unsigned int border_height = pattern_size.height;
++
++	if (pattern_size.isNull())
++		return {};
++
++	/* No need for top/bottom border with a pattern height of 2 */
++	if (pattern_size.height == 2)
++		border_height = 0;
++
++	/*
++	 * For debayer interpolation a border is kept around the entire image
++	 * and the minimum output size is pattern-height x pattern-width.
++	 */
++	if (inputSize.width < (3 * pattern_size.width) ||
++	    inputSize.height < (2 * border_height + 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 * border_height) & ~(pattern_size.height - 1)),
++			 pattern_size.width, pattern_size.height);
++}
++
++} /* namespace libcamera */
+diff --git a/src/libcamera/software_isp/debayer_cpu.h b/src/libcamera/software_isp/debayer_cpu.h
+new file mode 100644
+index 00000000..8a51ed85
+--- /dev/null
++++ b/src/libcamera/software_isp/debayer_cpu.h
+@@ -0,0 +1,143 @@
++/* 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 "debayer.h"
++#include "swstats_cpu.h"
++
++namespace libcamera {
++
++class DebayerCpu : public Debayer, public Object
++{
++public:
++	DebayerCpu(std::unique_ptr<SwStatsCpu> stats);
++	~DebayerCpu();
++
++	int configure(const StreamConfiguration &inputCfg,
++		      const std::vector<std::reference_wrapper<StreamConfiguration>> &outputCfgs);
++	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);
++	SizeRange sizes(PixelFormat inputFormat, const Size &inputSize);
++
++	/**
++	 * \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:
++	/**
++	 * \brief Called to debayer 1 line of Bayer input data to output format
++	 * \param[out] dst Pointer to the start of the output line to write
++	 * \param[in] src The input data
++	 *
++	 * Input data is an array of (patternSize_.height + 1) src
++	 * pointers each pointing to a line in the Bayer source. The middle
++	 * element of the array will point to the actual line being processed.
++	 * Earlier element(s) will point to the previous line(s) and later
++	 * element(s) to the next line(s).
++	 *
++	 * These functions take an array of src pointers, rather than
++	 * a single src pointer + a stride for the source, so that when the src
++	 * is slow uncached memory it can be copied to faster memory before
++	 * debayering. Debayering a standard 2x2 Bayer pattern requires access
++	 * to the previous and next src lines for interpolating the missing
++	 * colors. To allow copying the src lines only once 3 temporary buffers
++	 * each holding a single line are used, re-using the oldest buffer for
++	 * the next line and the pointers are swizzled so that:
++	 * src[0] = previous-line, src[1] = currrent-line, src[2] = next-line.
++	 * This way the 3 pointers passed to the debayer functions form
++	 * a sliding window over the src avoiding the need to copy each
++	 * line more than once.
++	 *
++	 * Similarly for bayer patterns which repeat every 4 lines, 5 src
++	 * pointers are passed holding: src[0] = 2-lines-up, src[1] = 1-line-up
++	 * src[2] = current-line, src[3] = 1-line-down, src[4] = 2-lines-down.
++	 */
++	using debayerFn = void (DebayerCpu::*)(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[]);
++	void debayer10P_GBGB_BGR888(uint8_t *dst, const uint8_t *src[]);
++	void debayer10P_RGRG_BGR888(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);
++	void setupInputMemcpy(const uint8_t *linePointers[]);
++	void shiftLinePointers(const uint8_t *linePointers[], const uint8_t *src);
++	void memcpyNextLine(const uint8_t *linePointers[]);
++	void process2(const uint8_t *src, uint8_t *dst);
++	void process4(const uint8_t *src, uint8_t *dst);
++
++	static constexpr unsigned int kGammaLookupSize = 1024;
++	static constexpr unsigned int kRGBLookupSize = 256;
++	/* Max. supported Bayer pattern height is 4, debayering this requires 5 lines */
++	static constexpr unsigned int kMaxLineBuffers = 5;
++
++	std::array<uint8_t, kGammaLookupSize> gamma_;
++	std::array<uint8_t, kRGBLookupSize> red_;
++	std::array<uint8_t, kRGBLookupSize> green_;
++	std::array<uint8_t, kRGBLookupSize> blue_;
++	debayerFn debayer0_;
++	debayerFn debayer1_;
++	debayerFn debayer2_;
++	debayerFn debayer3_;
++	Rectangle window_;
++	DebayerInputConfig inputConfig_;
++	DebayerOutputConfig outputConfig_;
++	std::unique_ptr<SwStatsCpu> stats_;
++	uint8_t *lineBuffers_[kMaxLineBuffers];
++	unsigned int lineBufferLength_;
++	unsigned int lineBufferPadding_;
++	unsigned int lineBufferIndex_;
++	bool enableInputMemcpy_;
++	float gamma_correction_;
++	unsigned int measuredFrames_;
++	int64_t frameProcessTime_;
++	/* Skip 30 frames for things to stabilize then measure 30 frames */
++	static constexpr unsigned int kFramesToSkip = 30;
++	static constexpr unsigned int kLastFrameToMeasure = 60;
++};
++
++} /* namespace libcamera */
+diff --git a/src/libcamera/software_isp/meson.build b/src/libcamera/software_isp/meson.build
+index 62095f61..71b46539 100644
+--- a/src/libcamera/software_isp/meson.build
++++ b/src/libcamera/software_isp/meson.build
+@@ -9,5 +9,6 @@ endif
+ 
+ libcamera_sources += files([
+     'debayer.cpp',
++    'debayer_cpu.cpp',
+     'swstats_cpu.cpp',
+ ])
+-- 
+2.43.2
+
diff --git a/users/flokli/ipu6-softisp/libcamera/0009-libcamera-ipa-add-Soft-IPA.patch b/users/flokli/ipu6-softisp/libcamera/0009-libcamera-ipa-add-Soft-IPA.patch
new file mode 100644
index 0000000000..40f9403ba9
--- /dev/null
+++ b/users/flokli/ipu6-softisp/libcamera/0009-libcamera-ipa-add-Soft-IPA.patch
@@ -0,0 +1,506 @@
+From 5261c801d8425fa82bcbd3da0199d06153eb5bd7 Mon Sep 17 00:00:00 2001
+From: Andrey Konovalov <andrey.konovalov@linaro.org>
+Date: Mon, 11 Mar 2024 15:15:13 +0100
+Subject: [PATCH 09/21] libcamera: ipa: add Soft IPA
+
+Define the Soft IPA main and event interfaces, add the Soft IPA
+implementation.
+
+The current src/ipa/meson.build assumes the IPA name to match the
+pipeline name. For this reason "-Dipas=simple" is used for the
+Soft IPA module.
+
+Auto exposure/gain and AWB implementation by Dennis, Toon and Martti.
+
+Auto exposure/gain targets a Mean Sample Value of 2.5 following
+the MSV calculation algorithm from:
+https://www.araa.asn.au/acra/acra2007/papers/paper84final.pdf
+
+Tested-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org> # sc8280xp Lenovo x13s
+Tested-by: Pavel Machek <pavel@ucw.cz>
+Reviewed-by: Pavel Machek <pavel@ucw.cz>
+Signed-off-by: Andrey Konovalov <andrey.konovalov@linaro.org>
+Co-developed-by: Dennis Bonke <admin@dennisbonke.com>
+Signed-off-by: Dennis Bonke <admin@dennisbonke.com>
+Co-developed-by: Marttico <g.martti@gmail.com>
+Signed-off-by: Marttico <g.martti@gmail.com>
+Co-developed-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>
+---
+ Documentation/Doxyfile.in         |   1 +
+ include/libcamera/ipa/meson.build |   1 +
+ include/libcamera/ipa/soft.mojom  |  28 +++
+ meson_options.txt                 |   2 +-
+ src/ipa/simple/data/meson.build   |   9 +
+ src/ipa/simple/data/soft.conf     |   3 +
+ src/ipa/simple/meson.build        |  25 +++
+ src/ipa/simple/soft_simple.cpp    | 326 ++++++++++++++++++++++++++++++
+ 8 files changed, 394 insertions(+), 1 deletion(-)
+ create mode 100644 include/libcamera/ipa/soft.mojom
+ create mode 100644 src/ipa/simple/data/meson.build
+ create mode 100644 src/ipa/simple/data/soft.conf
+ create mode 100644 src/ipa/simple/meson.build
+ create mode 100644 src/ipa/simple/soft_simple.cpp
+
+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..3352d08f 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': '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..c249bd75
+--- /dev/null
++++ b/include/libcamera/ipa/soft.mojom
+@@ -0,0 +1,28 @@
++/* SPDX-License-Identifier: LGPL-2.1-or-later */
++
++/*
++ * \todo Document the interface and remove the related EXCLUDE_PATTERNS entry.
++ */
++
++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/meson_options.txt b/meson_options.txt
+index 5fdc7be8..94372e47 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', 'vimc'],
+         description : 'Select which IPA modules to build')
+ 
+ option('lc-compliance',
+diff --git a/src/ipa/simple/data/meson.build b/src/ipa/simple/data/meson.build
+new file mode 100644
+index 00000000..33548cc6
+--- /dev/null
++++ b/src/ipa/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/data/soft.conf b/src/ipa/simple/data/soft.conf
+new file mode 100644
+index 00000000..0c70e7c0
+--- /dev/null
++++ b/src/ipa/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/meson.build b/src/ipa/simple/meson.build
+new file mode 100644
+index 00000000..3e863db7
+--- /dev/null
++++ b/src/ipa/simple/meson.build
+@@ -0,0 +1,25 @@
++# 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,
++                    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/soft_simple.cpp b/src/ipa/simple/soft_simple.cpp
+new file mode 100644
+index 00000000..312df4ba
+--- /dev/null
++++ b/src/ipa/simple/soft_simple.cpp
+@@ -0,0 +1,326 @@
++/* 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/base/shared_fd.h>
++
++#include <libcamera/control_ids.h>
++#include <libcamera/controls.h>
++
++#include <libcamera/ipa/ipa_interface.h>
++#include <libcamera/ipa/ipa_module_info.h>
++#include <libcamera/ipa/soft_ipa_interface.h>
++
++#include "libcamera/internal/camera_sensor.h"
++#include "libcamera/internal/software_isp/debayer_params.h"
++#include "libcamera/internal/software_isp/swisp_stats.h"
++
++namespace libcamera {
++
++LOG_DEFINE_CATEGORY(IPASoft)
++
++namespace ipa::soft {
++
++class IPASoftSimple : public ipa::soft::IPASoftInterface
++{
++public:
++	IPASoftSimple()
++		: params_(static_cast<DebayerParams *>(MAP_FAILED)),
++		  stats_(static_cast<SwIspStats *>(MAP_FAILED)), ignore_updates_(0)
++	{
++	}
++
++	~IPASoftSimple()
++	{
++		if (stats_ != MAP_FAILED)
++			munmap(stats_, sizeof(SwIspStats));
++		if (params_ != MAP_FAILED)
++			munmap(params_, sizeof(DebayerParams));
++	}
++
++	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;
++
++private:
++	void updateExposure(double exposureMSV);
++
++	SharedFD fdStats_;
++	SharedFD fdParams_;
++	DebayerParams *params_;
++	SwIspStats *stats_;
++
++	int32_t exposure_min_, exposure_max_;
++	int32_t again_min_, again_max_;
++	int32_t again_, exposure_;
++	unsigned int ignore_updates_;
++};
++
++int IPASoftSimple::init([[maybe_unused]] const IPASettings &settings,
++			const SharedFD &fdStats,
++			const SharedFD &fdParams,
++			const ControlInfoMap &sensorInfoMap)
++{
++	fdStats_ = fdStats;
++	if (!fdStats_.isValid()) {
++		LOG(IPASoft, Error) << "Invalid Statistics handle";
++		return -ENODEV;
++	}
++
++	fdParams_ = fdParams;
++	if (!fdParams_.isValid()) {
++		LOG(IPASoft, Error) << "Invalid Parameters handle";
++		return -ENODEV;
++	}
++
++	params_ = static_cast<DebayerParams *>(mmap(nullptr, sizeof(DebayerParams),
++						    PROT_WRITE, MAP_SHARED,
++						    fdParams_.get(), 0));
++	if (params_ == MAP_FAILED) {
++		LOG(IPASoft, Error) << "Unable to map Parameters";
++		return -errno;
++	}
++
++	stats_ = static_cast<SwIspStats *>(mmap(nullptr, sizeof(SwIspStats),
++						PROT_READ, MAP_SHARED,
++						fdStats_.get(), 0));
++	if (stats_ == MAP_FAILED) {
++		LOG(IPASoft, Error) << "Unable to map Statistics";
++		return -errno;
++	}
++
++	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::configure(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<int32_t>();
++	exposure_max_ = exposure_info.max().get<int32_t>();
++	if (!exposure_min_) {
++		LOG(IPASoft, Warning) << "Minimum exposure is zero, that can't be linear";
++		exposure_min_ = 1;
++	}
++
++	again_min_ = gain_info.min().get<int32_t>();
++	again_max_ = gain_info.max().get<int32_t>();
++	/*
++	 * The camera sensor gain (g) is usually not equal to the value written
++	 * into the gain register (x). But the way how the AGC algorithm changes
++	 * the gain value to make the total exposure closer to the optimum assumes
++	 * that g(x) is not too far from linear function. If the minimal gain is 0,
++	 * the g(x) is likely to be far from the linear, like g(x) = a / (b * x + c).
++	 * To avoid unexpected changes to the gain by the AGC algorithm (abrupt near
++	 * one edge, and very small near the other) we limit the range of the gain
++	 * values used.
++	 */
++	if (!again_min_) {
++		LOG(IPASoft, Warning) << "Minimum gain is zero, that can't be linear";
++		again_min_ = std::min(100, again_min_ / 2 + again_max_ / 2);
++	}
++
++	LOG(IPASoft, Info) << "Exposure " << exposure_min_ << "-" << exposure_max_
++			   << ", gain " << again_min_ << "-" << again_max_;
++
++	return 0;
++}
++
++int IPASoftSimple::start()
++{
++	return 0;
++}
++
++void IPASoftSimple::stop()
++{
++}
++
++/*
++ * The number of bins to use for the optimal exposure calculations.
++ */
++static constexpr unsigned int kExposureBinsCount = 5;
++/*
++ * The exposure is optimal when the mean sample value of the histogram is
++ * in the middle of the range.
++ */
++static constexpr float kExposureOptimal = kExposureBinsCount / 2.0;
++/*
++ * The below value implements the hysteresis for the exposure adjustment.
++ * It is small enough to have the exposure close to the optimal, and is big
++ * enough to prevent the exposure from wobbling around the optimal value.
++ */
++static constexpr float kExposureSatisfactory = 0.2;
++
++void IPASoftSimple::processStats(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;
++	}
++
++	/*
++	 * Calculate Mean Sample Value (MSV) according to formula from:
++	 * https://www.araa.asn.au/acra/acra2007/papers/paper84final.pdf
++	 */
++	constexpr unsigned int yHistValsPerBin =
++		SwIspStats::kYHistogramSize / kExposureBinsCount;
++	constexpr unsigned int yHistValsPerBinMod =
++		SwIspStats::kYHistogramSize /
++		(SwIspStats::kYHistogramSize % kExposureBinsCount + 1);
++	int ExposureBins[kExposureBinsCount] = {};
++	unsigned int denom = 0;
++	unsigned int num = 0;
++
++	for (unsigned int i = 0; i < SwIspStats::kYHistogramSize; i++) {
++		unsigned int idx = (i - (i / yHistValsPerBinMod)) / yHistValsPerBin;
++		ExposureBins[idx] += stats_->yHistogram[i];
++	}
++
++	for (unsigned int i = 0; i < kExposureBinsCount; i++) {
++		LOG(IPASoft, Debug) << i << ": " << ExposureBins[i];
++		denom += ExposureBins[i];
++		num += ExposureBins[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<int32_t>();
++	again_ = ctrls.get(V4L2_CID_ANALOGUE_GAIN).get<int32_t>();
++
++	updateExposure(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;
++}
++
++void IPASoftSimple::updateExposure(double exposureMSV)
++{
++	/* DENOMINATOR of 10 gives ~10% increment/decrement; DENOMINATOR of 5 - about ~20% */
++	static constexpr uint8_t kExpDenominator = 10;
++	static constexpr uint8_t kExpNumeratorUp = kExpDenominator + 1;
++	static constexpr uint8_t kExpNumeratorDown = kExpDenominator - 1;
++
++	int next;
++
++	if (exposureMSV < kExposureOptimal - kExposureSatisfactory) {
++		next = exposure_ * kExpNumeratorUp / kExpDenominator;
++		if (next - exposure_ < 1)
++			exposure_ += 1;
++		else
++			exposure_ = next;
++		if (exposure_ >= exposure_max_) {
++			next = again_ * kExpNumeratorUp / kExpDenominator;
++			if (next - again_ < 1)
++				again_ += 1;
++			else
++				again_ = next;
++		}
++	}
++
++	if (exposureMSV > kExposureOptimal + kExposureSatisfactory) {
++		if (exposure_ == exposure_max_ && again_ != again_min_) {
++			next = again_ * kExpNumeratorDown / kExpDenominator;
++			if (again_ - next < 1)
++				again_ -= 1;
++			else
++				again_ = next;
++		} else {
++			next = exposure_ * kExpNumeratorDown / kExpDenominator;
++			if (exposure_ - next < 1)
++				exposure_ -= 1;
++			else
++				exposure_ = next;
++		}
++	}
++
++	exposure_ = std::clamp(exposure_, exposure_min_, exposure_max_);
++	again_ = std::clamp(again_, again_min_, again_max_);
++}
++
++} /* namespace ipa::soft */
++
++/*
++ * External IPA module interface
++ */
++extern "C" {
++const struct IPAModuleInfo ipaModuleInfo = {
++	IPA_MODULE_API_VERSION,
++	0,
++	"SimplePipelineHandler",
++	"simple",
++};
++
++IPAInterface *ipaCreate()
++{
++	return new ipa::soft::IPASoftSimple();
++}
++
++} /* extern "C" */
++
++} /* namespace libcamera */
+-- 
+2.43.2
+
diff --git a/users/flokli/ipu6-softisp/libcamera/0010-libcamera-introduce-SoftwareIsp.patch b/users/flokli/ipu6-softisp/libcamera/0010-libcamera-introduce-SoftwareIsp.patch
new file mode 100644
index 0000000000..9f2d66c2f8
--- /dev/null
+++ b/users/flokli/ipu6-softisp/libcamera/0010-libcamera-introduce-SoftwareIsp.patch
@@ -0,0 +1,507 @@
+From ad41ea12fe4b8ca0ace20781c775a63ed0d66f4c Mon Sep 17 00:00:00 2001
+From: Andrey Konovalov <andrey.konovalov@linaro.org>
+Date: Mon, 11 Mar 2024 15:15:14 +0100
+Subject: [PATCH 10/21] libcamera: introduce SoftwareIsp
+
+Doxygen documentation by Dennis Bonke.
+
+Tested-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org> # sc8280xp Lenovo x13s
+Tested-by: Pavel Machek <pavel@ucw.cz>
+Reviewed-by: Pavel Machek <pavel@ucw.cz>
+Co-developed-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>
+---
+ .../internal/software_isp/meson.build         |   1 +
+ .../internal/software_isp/software_isp.h      |  98 +++++
+ src/libcamera/software_isp/meson.build        |   1 +
+ src/libcamera/software_isp/software_isp.cpp   | 349 ++++++++++++++++++
+ 4 files changed, 449 insertions(+)
+ create mode 100644 include/libcamera/internal/software_isp/software_isp.h
+ create mode 100644 src/libcamera/software_isp/software_isp.cpp
+
+diff --git a/include/libcamera/internal/software_isp/meson.build b/include/libcamera/internal/software_isp/meson.build
+index a620e16d..508ddddc 100644
+--- a/include/libcamera/internal/software_isp/meson.build
++++ b/include/libcamera/internal/software_isp/meson.build
+@@ -2,5 +2,6 @@
+ 
+ libcamera_internal_headers += files([
+     'debayer_params.h',
++    'software_isp.h',
+     'swisp_stats.h',
+ ])
+diff --git a/include/libcamera/internal/software_isp/software_isp.h b/include/libcamera/internal/software_isp/software_isp.h
+new file mode 100644
+index 00000000..8d25e979
+--- /dev/null
++++ b/include/libcamera/internal/software_isp/software_isp.h
+@@ -0,0 +1,98 @@
++/* SPDX-License-Identifier: LGPL-2.1-or-later */
++/*
++ * Copyright (C) 2023, Linaro Ltd
++ *
++ * software_isp.h - Simple software ISP implementation
++ */
++
++#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/base/thread.h>
++
++#include <libcamera/geometry.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/pipeline_handler.h"
++#include "libcamera/internal/shared_mem_object.h"
++#include "libcamera/internal/software_isp/debayer_params.h"
++
++namespace libcamera {
++
++class DebayerCpu;
++class FrameBuffer;
++class PixelFormat;
++struct StreamConfiguration;
++
++LOG_DECLARE_CATEGORY(SoftwareIsp)
++
++class SoftwareIsp
++{
++public:
++	SoftwareIsp(PipelineHandler *pipe, const ControlInfoMap &sensorControls);
++	~SoftwareIsp();
++
++	int loadConfiguration([[maybe_unused]] const std::string &filename) { return 0; }
++
++	bool isValid() const;
++
++	std::vector<PixelFormat> formats(PixelFormat input);
++
++	SizeRange sizes(PixelFormat inputFormat, const Size &inputSize);
++
++	std::tuple<unsigned int, unsigned int>
++	strideAndFrameSize(const PixelFormat &outputFormat, const Size &size);
++
++	int configure(const StreamConfiguration &inputCfg,
++		      const std::vector<std::reference_wrapper<StreamConfiguration>> &outputCfgs,
++		      const ControlInfoMap &sensorControls);
++
++	int exportBuffers(unsigned int output, unsigned int count,
++			  std::vector<std::unique_ptr<FrameBuffer>> *buffers);
++
++	void processStats(const ControlList &sensorControls);
++
++	int start();
++	void stop();
++
++	int queueBuffers(FrameBuffer *input,
++			 const std::map<unsigned int, FrameBuffer *> &outputs);
++
++	void process(FrameBuffer *input, FrameBuffer *output);
++
++	Signal<FrameBuffer *> inputBufferReady;
++	Signal<FrameBuffer *> outputBufferReady;
++	Signal<int> ispStatsReady;
++	Signal<const ControlList &> setSensorControls;
++
++private:
++	void saveIspParams(int dummy);
++	void setSensorCtrls(const ControlList &sensorControls);
++	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 71b46539..e9266e54 100644
+--- a/src/libcamera/software_isp/meson.build
++++ b/src/libcamera/software_isp/meson.build
+@@ -10,5 +10,6 @@ endif
+ libcamera_sources += files([
+     'debayer.cpp',
+     'debayer_cpu.cpp',
++    'software_isp.cpp',
+     'swstats_cpu.cpp',
+ ])
+diff --git a/src/libcamera/software_isp/software_isp.cpp b/src/libcamera/software_isp/software_isp.cpp
+new file mode 100644
+index 00000000..388b4496
+--- /dev/null
++++ b/src/libcamera/software_isp/software_isp.cpp
+@@ -0,0 +1,349 @@
++/* SPDX-License-Identifier: LGPL-2.1-or-later */
++/*
++ * Copyright (C) 2023, Linaro Ltd
++ *
++ * software_isp.cpp - Simple software ISP implementation
++ */
++
++#include "libcamera/internal/software_isp/software_isp.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"
++
++#include "debayer_cpu.h"
++
++/**
++ * \file software_isp.cpp
++ * \brief Simple software ISP implementation
++ */
++
++namespace libcamera {
++
++LOG_DEFINE_CATEGORY(SoftwareIsp)
++
++/**
++ * \class SoftwareIsp
++ * \brief Class for the Software ISP
++ */
++
++/**
++ * \var SoftwareIsp::inputBufferReady
++ * \brief A signal emitted when the input frame buffer completes
++ */
++
++/**
++ * \var SoftwareIsp::outputBufferReady
++ * \brief A signal emitted when the output frame buffer completes
++ */
++
++/**
++ * \var SoftwareIsp::ispStatsReady
++ * \brief A signal emitted when the statistics for IPA are ready
++ *
++ * The int parameter isn't actually used.
++ */
++
++/**
++ * \var SoftwareIsp::setSensorControls
++ * \brief A signal emitted when the values to write to the sensor controls are ready
++ */
++
++/**
++ * \brief Constructs SoftwareIsp object
++ * \param[in] pipe The pipeline handler in use
++ * \param[in] sensorControls ControlInfoMap describing the controls supported by the sensor
++ */
++SoftwareIsp::SoftwareIsp(PipelineHandler *pipe, const ControlInfoMap &sensorControls)
++	: debayer_(nullptr),
++	  debayerParams_{ DebayerParams::kGain10, DebayerParams::kGain10, DebayerParams::kGain10, 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_) {
++		LOG(SoftwareIsp, Error) << "Failed to create shared memory for parameters";
++		return;
++	}
++
++	auto stats = std::make_unique<SwStatsCpu>();
++	if (!stats->isValid()) {
++		LOG(SoftwareIsp, Error) << "Failed to create SwStatsCpu object";
++		return;
++	}
++	stats->statsReady.connect(this, &SoftwareIsp::statsReady);
++
++	debayer_ = std::make_unique<DebayerCpu>(std::move(stats));
++	debayer_->inputBufferReady.connect(this, &SoftwareIsp::inputReady);
++	debayer_->outputBufferReady.connect(this, &SoftwareIsp::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, &SoftwareIsp::saveIspParams);
++	ipa_->setSensorControls.connect(this, &SoftwareIsp::setSensorCtrls);
++
++	debayer_->moveToThread(&ispWorkerThread_);
++}
++
++SoftwareIsp::~SoftwareIsp()
++{
++	/* make sure to destroy the DebayerCpu before the ispWorkerThread_ is gone */
++	debayer_.reset();
++}
++
++/**
++ * \fn int SoftwareIsp::loadConfiguration([[maybe_unused]] const std::string &filename)
++ * \brief Load a configuration from a file
++ * \param[in] filename The file to load the configuration data from
++ *
++ * Currently is a stub doing nothing and always returning "success".
++ *
++ * \return 0 on success
++ */
++
++/**
++ * \brief Process the statistics gathered
++ * \param[in] sensorControls The sensor controls
++ *
++ * Requests the IPA to calculate new parameters for ISP and new control
++ * values for the sensor.
++ */
++void SoftwareIsp::processStats(const ControlList &sensorControls)
++{
++	ASSERT(ipa_);
++	ipa_->processStats(sensorControls);
++}
++
++/**
++ * \brief Check the validity of Software Isp object
++ * \return True if Software Isp is valid, false otherwise
++ */
++bool SoftwareIsp::isValid() const
++{
++	return !!debayer_;
++}
++
++/**
++  * \brief Get the output formats supported for the given input format
++  * \param[in] inputFormat The input format
++  * \return All the supported output formats or an empty vector if there are none
++  */
++std::vector<PixelFormat> SoftwareIsp::formats(PixelFormat inputFormat)
++{
++	ASSERT(debayer_ != nullptr);
++
++	return debayer_->formats(inputFormat);
++}
++
++/**
++ * \brief Get the supported output sizes for the given input format and size
++ * \param[in] inputFormat The input format
++ * \param[in] inputSize The input frame size
++ * \return The valid size range or an empty range if there are none
++ */
++SizeRange SoftwareIsp::sizes(PixelFormat inputFormat, const Size &inputSize)
++{
++	ASSERT(debayer_ != nullptr);
++
++	return debayer_->sizes(inputFormat, inputSize);
++}
++
++/**
++ * Get the output stride and the frame size in bytes for the given output format and size
++ * \param[in] outputFormat The output format
++ * \param[in] size The output size (width and height in pixels)
++ * \return A tuple of the stride and the frame size in bytes, or a tuple of 0,0
++ * if there is no valid output config
++ */
++std::tuple<unsigned int, unsigned int>
++SoftwareIsp::strideAndFrameSize(const PixelFormat &outputFormat, const Size &size)
++{
++	ASSERT(debayer_ != nullptr);
++
++	return debayer_->strideAndFrameSize(outputFormat, size);
++}
++
++/**
++ * \brief Configure the SoftwareIsp object according to the passed in parameters
++ * \param[in] inputCfg The input configuration
++ * \param[in] outputCfgs The output configurations
++ * \param[in] sensorControls ControlInfoMap of the controls supported by the sensor
++ * \return 0 on success, a negative errno on failure
++ */
++int SoftwareIsp::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);
++}
++
++/**
++ * \brief Export the buffers from the Software ISP
++ * \param[in] output Output stream index exporting the buffers
++ * \param[in] count Number of buffers to allocate
++ * \param[out] buffers Vector to store the allocated buffers
++ * \return The number of allocated buffers on success or a negative error code
++ * otherwise
++ */
++int SoftwareIsp::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;
++}
++
++/**
++ * \brief Queue buffers to Software ISP
++ * \param[in] input The input framebuffer
++ * \param[in] outputs The container holding the output stream indexes and
++ * their respective frame buffer outputs
++ * \return 0 on success, a negative errno on failure
++ */
++int SoftwareIsp::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;
++}
++
++/**
++ * \brief Starts the Software ISP streaming operation
++ * \return 0 on success, any other value indicates an error
++ */
++int SoftwareIsp::start()
++{
++	int ret = ipa_->start();
++	if (ret)
++		return ret;
++
++	ispWorkerThread_.start();
++	return 0;
++}
++
++/**
++ * \brief Stops the Software ISP streaming operation
++ */
++void SoftwareIsp::stop()
++{
++	ispWorkerThread_.exit();
++	ispWorkerThread_.wait();
++
++	ipa_->stop();
++}
++
++/**
++ * \brief Passes the input framebuffer to the ISP worker to process
++ * \param[in] input The input framebuffer
++ * \param[out] output The framebuffer to write the processed frame to
++ */
++void SoftwareIsp::process(FrameBuffer *input, FrameBuffer *output)
++{
++	debayer_->invokeMethod(&DebayerCpu::process,
++			       ConnectionTypeQueued, input, output, debayerParams_);
++}
++
++void SoftwareIsp::saveIspParams([[maybe_unused]] int dummy)
++{
++	debayerParams_ = *sharedParams_;
++}
++
++void SoftwareIsp::setSensorCtrls(const ControlList &sensorControls)
++{
++	setSensorControls.emit(sensorControls);
++}
++
++void SoftwareIsp::statsReady(int dummy)
++{
++	ispStatsReady.emit(dummy);
++}
++
++void SoftwareIsp::inputReady(FrameBuffer *input)
++{
++	inputBufferReady.emit(input);
++}
++
++void SoftwareIsp::outputReady(FrameBuffer *output)
++{
++	outputBufferReady.emit(output);
++}
++
++} /* namespace libcamera */
+-- 
+2.43.2
+
diff --git a/users/flokli/ipu6-softisp/libcamera/0011-libcamera-pipeline-simple-rename-converterBuffers_-a.patch b/users/flokli/ipu6-softisp/libcamera/0011-libcamera-pipeline-simple-rename-converterBuffers_-a.patch
new file mode 100644
index 0000000000..5c2237a8eb
--- /dev/null
+++ b/users/flokli/ipu6-softisp/libcamera/0011-libcamera-pipeline-simple-rename-converterBuffers_-a.patch
@@ -0,0 +1,240 @@
+From 050440eed6ab90686df217f5ff7dea0b241e3898 Mon Sep 17 00:00:00 2001
+From: Andrey Konovalov <andrey.konovalov@linaro.org>
+Date: Mon, 11 Mar 2024 15:15:15 +0100
+Subject: [PATCH 11/21] 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.
+
+Tested-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org> # sc8280xp Lenovo x13s
+Tested-by: Pavel Machek <pavel@ucw.cz>
+Reviewed-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
+Reviewed-by: Pavel Machek <pavel@ucw.cz>
+Signed-off-by: Andrey Konovalov <andrey.konovalov@linaro.org>
+Signed-off-by: Hans de Goede <hdegoede@redhat.com>
+---
+ 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 a84f6760..78854ef8 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();
+ 
+@@ -1189,14 +1190,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);
+ 	}
+ 
+@@ -1222,7 +1223,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
+@@ -1243,13 +1244,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];
+@@ -1268,7 +1269,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);
+@@ -1276,7 +1277,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());
+ 	}
+ 
+@@ -1288,7 +1289,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();
+@@ -1296,7 +1297,7 @@ void SimplePipelineHandler::stopDevice(Camera *camera)
+ 
+ 	video->bufferReady.disconnect(data, &SimpleCameraData::bufferReady);
+ 
+-	data->converterBuffers_.clear();
++	data->conversionBuffers_.clear();
+ 
+ 	releasePipeline(data);
+ }
+@@ -1314,7 +1315,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);
+@@ -1323,8 +1324,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.2
+
diff --git a/users/flokli/ipu6-softisp/libcamera/0012-libcamera-pipeline-simple-enable-use-of-Soft-ISP-and.patch b/users/flokli/ipu6-softisp/libcamera/0012-libcamera-pipeline-simple-enable-use-of-Soft-ISP-and.patch
new file mode 100644
index 0000000000..378a43604f
--- /dev/null
+++ b/users/flokli/ipu6-softisp/libcamera/0012-libcamera-pipeline-simple-enable-use-of-Soft-ISP-and.patch
@@ -0,0 +1,302 @@
+From d64b0fca22ef25b8a14d7fc97dfab64eb1c4f21a Mon Sep 17 00:00:00 2001
+From: Andrey Konovalov <andrey.konovalov@linaro.org>
+Date: Mon, 11 Mar 2024 15:15:16 +0100
+Subject: [PATCH 12/21] 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 -Dipas=simple
+
+Also using the Soft ISP for the particular hardware platform must
+be enabled in the supportedDevices[] table.
+
+If the pipeline uses Converter, Soft ISP and Soft IPA aren't
+available.
+
+Tested-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org> # sc8280xp Lenovo x13s
+Tested-by: Pavel Machek <pavel@ucw.cz>
+Reviewed-by: Pavel Machek <pavel@ucw.cz>
+Signed-off-by: Andrey Konovalov <andrey.konovalov@linaro.org>
+Signed-off-by: Hans de Goede <hdegoede@redhat.com>
+---
+ src/libcamera/pipeline/simple/simple.cpp | 137 ++++++++++++++++++-----
+ 1 file changed, 109 insertions(+), 28 deletions(-)
+
+diff --git a/src/libcamera/pipeline/simple/simple.cpp b/src/libcamera/pipeline/simple/simple.cpp
+index 78854ef8..c3ebb7b7 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/software_isp.h"
+ #include "libcamera/internal/v4l2_subdevice.h"
+ #include "libcamera/internal/v4l2_videodevice.h"
+ 
+@@ -185,17 +186,22 @@ struct SimplePipelineInfo {
+ 	 * and the number of streams it supports.
+ 	 */
+ 	std::vector<std::pair<const char *, unsigned int>> converters;
++	/*
++	 * Using Software ISP is to be enabled per driver.
++	 * The Software ISP can't be used together with the converters.
++	 */
++	bool swIspEnabled;
+ };
+ 
+ namespace {
+ 
+ static const SimplePipelineInfo supportedDevices[] = {
+-	{ "dcmipp", {} },
+-	{ "imx7-csi", { { "pxp", 1 } } },
+-	{ "j721e-csi2rx", {} },
+-	{ "mxc-isi", {} },
+-	{ "qcom-camss", {} },
+-	{ "sun6i-csi", {} },
++	{ "dcmipp", {}, false },
++	{ "imx7-csi", { { "pxp", 1 } }, false },
++	{ "j721e-csi2rx", {}, false },
++	{ "mxc-isi", {}, false },
++	{ "qcom-camss", {}, true },
++	{ "sun6i-csi", {}, false },
+ };
+ 
+ } /* namespace */
+@@ -274,6 +280,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 +288,9 @@ private:
+ 
+ 	void conversionInputDone(FrameBuffer *buffer);
+ 	void conversionOutputDone(FrameBuffer *buffer);
++
++	void ispStatsReady(int dummy);
++	void setSensorControls(const ControlList &sensorControls);
+ };
+ 
+ class SimpleCameraConfiguration : public CameraConfiguration
+@@ -332,6 +342,7 @@ public:
+ 	V4L2VideoDevice *video(const MediaEntity *entity);
+ 	V4L2Subdevice *subdev(const MediaEntity *entity);
+ 	MediaDevice *converter() { return converter_; }
++	bool swIspEnabled() { return swIspEnabled_; }
+ 
+ protected:
+ 	int queueRequestDevice(Camera *camera, Request *request) override;
+@@ -360,6 +371,7 @@ private:
+ 	std::map<const MediaEntity *, EntityData> entities_;
+ 
+ 	MediaDevice *converter_;
++	bool swIspEnabled_;
+ };
+ 
+ /* -----------------------------------------------------------------------------
+@@ -509,6 +521,29 @@ int SimpleCameraData::init()
+ 		}
+ 	}
+ 
++	/*
++	 * Instantiate Soft ISP if this is enabled for the given driver and no converter is used.
++	 */
++	if (!converter_ && pipe->swIspEnabled()) {
++		swIsp_ = std::make_unique<SoftwareIsp>(pipe, sensor_->controls());
++		if (!swIsp_->isValid()) {
++			LOG(SimplePipeline, Warning)
++				<< "Failed to create software ISP, disabling software debayering";
++			swIsp_.reset();
++		} else {
++			/*
++			 * \todo explain why SimpleCameraData::conversionInputDone() can't be directly
++			 * connected to inputBufferReady signal.
++			 */
++			swIsp_->inputBufferReady.connect(pipe, [this](FrameBuffer *buffer) {
++				this->conversionInputDone(buffer);
++			});
++			swIsp_->outputBufferReady.connect(this, &SimpleCameraData::conversionOutputDone);
++			swIsp_->ispStatsReady.connect(this, &SimpleCameraData::ispStatsReady);
++			swIsp_->setSensorControls.connect(this, &SimpleCameraData::setSensorControls);
++		}
++	}
++
+ 	video_ = pipe->video(entities_.back().entity);
+ 	ASSERT(video_);
+ 
+@@ -599,12 +634,21 @@ void SimpleCameraData::tryPipeline(unsigned int code, const Size &size)
+ 		config.captureFormat = pixelFormat;
+ 		config.captureSize = format.size;
+ 
+-		if (!converter_) {
++
++		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;
+-		} else {
+-			config.outputFormats = converter_->formats(pixelFormat);
+-			config.outputSizes = converter_->sizes(format.size);
+ 		}
+ 
+ 		configs_.push_back(config);
+@@ -750,9 +794,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 +842,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 +852,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 +882,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)
+ {
+@@ -1046,8 +1106,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 {
+@@ -1210,7 +1272,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,
+@@ -1224,8 +1288,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);
+ }
+@@ -1270,10 +1336,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. */
+@@ -1289,8 +1363,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();
+@@ -1452,6 +1531,8 @@ bool SimplePipelineHandler::match(DeviceEnumerator *enumerator)
+ 		}
+ 	}
+ 
++  swIspEnabled_ = info->swIspEnabled;
++
+ 	/* Locate the sensors. */
+ 	std::vector<MediaEntity *> sensors = locateSensors();
+ 	if (sensors.empty()) {
+-- 
+2.43.2
+
diff --git a/users/flokli/ipu6-softisp/libcamera/0013-libcamera-swstats_cpu-Add-support-for-8-10-and-12-bp.patch b/users/flokli/ipu6-softisp/libcamera/0013-libcamera-swstats_cpu-Add-support-for-8-10-and-12-bp.patch
new file mode 100644
index 0000000000..1a57d690ff
--- /dev/null
+++ b/users/flokli/ipu6-softisp/libcamera/0013-libcamera-swstats_cpu-Add-support-for-8-10-and-12-bp.patch
@@ -0,0 +1,203 @@
+From aabc53453d542495d9da25411f57308c01f2bc28 Mon Sep 17 00:00:00 2001
+From: Hans de Goede <hdegoede@redhat.com>
+Date: Mon, 11 Mar 2024 15:15:17 +0100
+Subject: [PATCH 13/21] 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>
+Reviewed-by: Pavel Machek <pavel@ucw.cz>
+Reviewed-by: Milan Zamazal <mzamazal@redhat.com>
+Signed-off-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
+Signed-off-by: Hans de Goede <hdegoede@redhat.com>
+---
+ src/libcamera/software_isp/swstats_cpu.cpp | 128 +++++++++++++++++++++
+ src/libcamera/software_isp/swstats_cpu.h   |   9 ++
+ 2 files changed, 137 insertions(+)
+
+diff --git a/src/libcamera/software_isp/swstats_cpu.cpp b/src/libcamera/software_isp/swstats_cpu.cpp
+index 448d0e4c..be310f56 100644
+--- a/src/libcamera/software_isp/swstats_cpu.cpp
++++ b/src/libcamera/software_isp/swstats_cpu.cpp
+@@ -71,6 +71,83 @@ static const unsigned int kBlueYMul = 29; /* 0.114 * 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;
++
++	SWSTATS_START_LINE_STATS(uint8_t)
++
++	if (swapLines_)
++		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;
++
++		SWSTATS_ACCUMULATE_LINE_STATS(1)
++	}
++
++	SWSTATS_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;
++
++	SWSTATS_START_LINE_STATS(uint16_t)
++
++	if (swapLines_)
++		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 */
++		SWSTATS_ACCUMULATE_LINE_STATS(4)
++	}
++
++	SWSTATS_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;
++
++	SWSTATS_START_LINE_STATS(uint16_t)
++
++	if (swapLines_)
++		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 */
++		SWSTATS_ACCUMULATE_LINE_STATS(16)
++	}
++
++	SWSTATS_FINISH_LINE_STATS()
++}
++
+ void SwStatsCpu::statsBGGR10PLine0(const uint8_t *src[])
+ {
+ 	const uint8_t *src0 = src[1] + window_.x * 5 / 4;
+@@ -147,6 +224,42 @@ void SwStatsCpu::finishFrame(void)
+ 	statsReady.emit(0);
+ }
+ 
++/**
++ * \brief Setup SwStatsCpu object for standard Bayer orders
++ * \param[in] order The Bayer order
++ *
++ * Check if order is a standard Bayer order and setup xShift_ and swapLines_
++ * 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:
++		xShift_ = 0;
++		swapLines_ = false;
++		break;
++	case BayerFormat::GBRG:
++		xShift_ = 1; /* BGGR -> GBRG */
++		swapLines_ = false;
++		break;
++	case BayerFormat::GRBG:
++		xShift_ = 0;
++		swapLines_ = true; /* BGGR -> GRBG */
++		break;
++	case BayerFormat::RGGB:
++		xShift_ = 1; /* BGGR -> GBRG */
++		swapLines_ = true; /* GBRG -> RGGB */
++		break;
++	default:
++		return -EINVAL;
++	}
++
++	patternSize_.height = 2;
++	patternSize_.width = 2;
++	ySkipMask_ = 0x02; /* Skip every 3th and 4th line */
++	return 0;
++}
++
+ /**
+  * \brief Configure the statistics object for the passed in input format.
+  * \param[in] inputCfg The input format
+@@ -158,6 +271,21 @@ int SwStatsCpu::configure(const StreamConfiguration &inputCfg)
+ 	BayerFormat bayerFormat =
+ 		BayerFormat::fromPixelFormat(inputCfg.pixelFormat);
+ 
++	if (bayerFormat.packing == BayerFormat::Packing::None &&
++	    setupStandardBayerOrder(bayerFormat.order) == 0) {
++		switch (bayerFormat.bitDepth) {
++		case 8:
++			stats0_ = &SwStatsCpu::statsBGGR8Line0;
++			return 0;
++		case 10:
++			stats0_ = &SwStatsCpu::statsBGGR10Line0;
++			return 0;
++		case 12:
++			stats0_ = &SwStatsCpu::statsBGGR12Line0;
++			return 0;
++		}
++	}
++
+ 	if (bayerFormat.bitDepth == 10 &&
+ 	    bayerFormat.packing == BayerFormat::Packing::CSI2) {
+ 		patternSize_.height = 2;
+diff --git a/src/libcamera/software_isp/swstats_cpu.h b/src/libcamera/software_isp/swstats_cpu.h
+index 0ac9ae71..bbbcf69b 100644
+--- a/src/libcamera/software_isp/swstats_cpu.h
++++ b/src/libcamera/software_isp/swstats_cpu.h
+@@ -17,6 +17,7 @@
+ 
+ #include <libcamera/geometry.h>
+ 
++#include "libcamera/internal/bayer_format.h"
+ #include "libcamera/internal/shared_mem_object.h"
+ #include "libcamera/internal/software_isp/swisp_stats.h"
+ 
+@@ -120,6 +121,14 @@ private:
+ 	 */
+ 	using statsProcessFn = void (SwStatsCpu::*)(const uint8_t *src[]);
+ 
++	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[]);
+ 
+-- 
+2.43.2
+
diff --git a/users/flokli/ipu6-softisp/libcamera/0014-libcamera-debayer_cpu-Add-support-for-8-10-and-12-bp.patch b/users/flokli/ipu6-softisp/libcamera/0014-libcamera-debayer_cpu-Add-support-for-8-10-and-12-bp.patch
new file mode 100644
index 0000000000..c7edf49828
--- /dev/null
+++ b/users/flokli/ipu6-softisp/libcamera/0014-libcamera-debayer_cpu-Add-support-for-8-10-and-12-bp.patch
@@ -0,0 +1,234 @@
+From 5f3647bd4f12dd62256a425c49fd18a0f5990930 Mon Sep 17 00:00:00 2001
+From: Hans de Goede <hdegoede@redhat.com>
+Date: Mon, 11 Mar 2024 15:15:18 +0100
+Subject: [PATCH 14/21] 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>
+Reviewed-by: Pavel Machek <pavel@ucw.cz>
+Reviewed-by: Milan Zamazal <mzamazal@redhat.com>
+Signed-off-by: Kieran Bingham <kieran.bingham@ideasonboard.com>
+Signed-off-by: Hans de Goede <hdegoede@redhat.com>
+---
+ src/libcamera/software_isp/debayer_cpu.cpp | 128 +++++++++++++++++++++
+ src/libcamera/software_isp/debayer_cpu.h   |  13 +++
+ 2 files changed, 141 insertions(+)
+
+diff --git a/src/libcamera/software_isp/debayer_cpu.cpp b/src/libcamera/software_isp/debayer_cpu.cpp
+index f932362c..eb1c2718 100644
+--- a/src/libcamera/software_isp/debayer_cpu.cpp
++++ b/src/libcamera/software_isp/debayer_cpu.cpp
+@@ -56,6 +56,11 @@ DebayerCpu::~DebayerCpu()
+ 		free(lineBuffers_[i]);
+ }
+ 
++#define DECLARE_SRC_POINTERS(pixel_t)                            \
++	const pixel_t *prev = (const pixel_t *)src[0] + xShift_; \
++	const pixel_t *curr = (const pixel_t *)src[1] + xShift_; \
++	const pixel_t *next = (const pixel_t *)src[2] + xShift_;
++
+ // RGR
+ // GBG
+ // RGR
+@@ -92,6 +97,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;
+@@ -193,6 +262,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)) {
+@@ -220,12 +299,61 @@ int DebayerCpu::getOutputConfig(PixelFormat outputFormat, DebayerOutputConfig &c
+ 	return -EINVAL;
+ }
+ 
++/*
++ * Check for standard Bayer orders and set xShift_ 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:
++		xShift_ = 1; /* BGGR -> GBRG */
++		break;
++	case BayerFormat::GRBG:
++		std::swap(debayer0_, debayer1_); /* BGGR -> GRBG */
++		break;
++	case BayerFormat::RGGB:
++		xShift_ = 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);
+ 
++	xShift_ = 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) {
+diff --git a/src/libcamera/software_isp/debayer_cpu.h b/src/libcamera/software_isp/debayer_cpu.h
+index 8a51ed85..fd1fa180 100644
+--- a/src/libcamera/software_isp/debayer_cpu.h
++++ b/src/libcamera/software_isp/debayer_cpu.h
+@@ -17,6 +17,8 @@
+ 
+ #include <libcamera/base/object.h>
+ 
++#include "libcamera/internal/bayer_format.h"
++
+ #include "debayer.h"
+ #include "swstats_cpu.h"
+ 
+@@ -82,6 +84,15 @@ private:
+ 	 */
+ 	using debayerFn = void (DebayerCpu::*)(uint8_t *dst, const uint8_t *src[]);
+ 
++	/* 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);
+ 	void setupInputMemcpy(const uint8_t *linePointers[]);
+ 	void shiftLinePointers(const uint8_t *linePointers[], const uint8_t *src);
+@@ -131,6 +143,7 @@ private:
+ 	unsigned int lineBufferLength_;
+ 	unsigned int lineBufferPadding_;
+ 	unsigned int lineBufferIndex_;
++	unsigned int xShift_; /* Offset of 0/1 applied to window_.x */
+ 	bool enableInputMemcpy_;
+ 	float gamma_correction_;
+ 	unsigned int measuredFrames_;
+-- 
+2.43.2
+
diff --git a/users/flokli/ipu6-softisp/libcamera/0015-libcamera-debayer_cpu-Add-BGR888-output-support.patch b/users/flokli/ipu6-softisp/libcamera/0015-libcamera-debayer_cpu-Add-BGR888-output-support.patch
new file mode 100644
index 0000000000..0abca2ea82
--- /dev/null
+++ b/users/flokli/ipu6-softisp/libcamera/0015-libcamera-debayer_cpu-Add-BGR888-output-support.patch
@@ -0,0 +1,127 @@
+From 186db51d54bcbd4d5096bea1e4396966c2dad001 Mon Sep 17 00:00:00 2001
+From: Hans de Goede <hdegoede@redhat.com>
+Date: Mon, 11 Mar 2024 15:15:19 +0100
+Subject: [PATCH 15/21] 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.
+
+Tested-by: Bryan O'Donoghue <bryan.odonoghue@linaro.org> # sc8280xp Lenovo x13s
+Tested-by: Pavel Machek <pavel@ucw.cz>
+Reviewed-by: Milan Zamazal <mzamazal@redhat.com>
+Signed-off-by: Hans de Goede <hdegoede@redhat.com>
+Reviewed-by: Stefan Klug <stefan.klug@ideasonboard.com>
+---
+ src/libcamera/software_isp/debayer_cpu.cpp | 42 +++++++++++++++++++---
+ src/libcamera/software_isp/debayer_cpu.h   |  1 +
+ 2 files changed, 38 insertions(+), 5 deletions(-)
+
+diff --git a/src/libcamera/software_isp/debayer_cpu.cpp b/src/libcamera/software_isp/debayer_cpu.cpp
+index eb1c2718..a1692693 100644
+--- a/src/libcamera/software_isp/debayer_cpu.cpp
++++ b/src/libcamera/software_isp/debayer_cpu.cpp
+@@ -268,7 +268,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;
+ 	}
+ 
+@@ -278,7 +278,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;
+ 	}
+ 
+@@ -289,7 +289,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;
+ 	}
+@@ -325,13 +325,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);
+ 
+ 	xShift_ = 0;
++	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;
++	}
+ 
+ 	if ((bayerFormat.bitDepth == 8 || bayerFormat.bitDepth == 10 || bayerFormat.bitDepth == 12) &&
+ 	    bayerFormat.packing == BayerFormat::Packing::None &&
+@@ -378,6 +406,7 @@ int DebayerCpu::setDebayerFunctions(PixelFormat inputFormat, [[maybe_unused]] Pi
+ 		}
+ 	}
+ 
++invalid_fmt:
+ 	LOG(Debayer, Error) << "Unsupported input output format combination";
+ 	return -EINVAL;
+ }
+@@ -661,6 +690,9 @@ void DebayerCpu::process(FrameBuffer *input, FrameBuffer *output, DebayerParams
+ 		gamma_correction_ = params.gamma;
+ 	}
+ 
++	if (swapRedBlueGains_)
++		std::swap(params.gainR, params.gainB);
++
+ 	for (unsigned int i = 0; i < kRGBLookupSize; i++) {
+ 		constexpr unsigned int div =
+ 			kRGBLookupSize * DebayerParams::kGain10 / kGammaLookupSize;
+diff --git a/src/libcamera/software_isp/debayer_cpu.h b/src/libcamera/software_isp/debayer_cpu.h
+index fd1fa180..5f44fc65 100644
+--- a/src/libcamera/software_isp/debayer_cpu.h
++++ b/src/libcamera/software_isp/debayer_cpu.h
+@@ -145,6 +145,7 @@ private:
+ 	unsigned int lineBufferIndex_;
+ 	unsigned int xShift_; /* Offset of 0/1 applied to window_.x */
+ 	bool enableInputMemcpy_;
++	bool swapRedBlueGains_;
+ 	float gamma_correction_;
+ 	unsigned int measuredFrames_;
+ 	int64_t frameProcessTime_;
+-- 
+2.43.2
+
diff --git a/users/flokli/ipu6-softisp/libcamera/0016-libcamera-Add-support-for-IGIG_GBGR_IGIG_GRGB-bayer-.patch b/users/flokli/ipu6-softisp/libcamera/0016-libcamera-Add-support-for-IGIG_GBGR_IGIG_GRGB-bayer-.patch
new file mode 100644
index 0000000000..724b67033f
--- /dev/null
+++ b/users/flokli/ipu6-softisp/libcamera/0016-libcamera-Add-support-for-IGIG_GBGR_IGIG_GRGB-bayer-.patch
@@ -0,0 +1,237 @@
+From e9580d30a1a79fce1ebd72ae74ceb4a3d1cf8fbb 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 16/21] 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.2
+
diff --git a/users/flokli/ipu6-softisp/libcamera/0017-libcamera-Add-Software-ISP-benchmarking-documentatio.patch b/users/flokli/ipu6-softisp/libcamera/0017-libcamera-Add-Software-ISP-benchmarking-documentatio.patch
new file mode 100644
index 0000000000..2343e9c46f
--- /dev/null
+++ b/users/flokli/ipu6-softisp/libcamera/0017-libcamera-Add-Software-ISP-benchmarking-documentatio.patch
@@ -0,0 +1,132 @@
+From 6c509a3d144d46a11454d32d128d16e16602b50f Mon Sep 17 00:00:00 2001
+From: Hans de Goede <hdegoede@redhat.com>
+Date: Mon, 11 Mar 2024 15:15:20 +0100
+Subject: [PATCH 17/21] 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.
+
+Reviewed-by: Milan Zamazal <mzamazal@redhat.com>
+Signed-off-by: Hans de Goede <hdegoede@redhat.com>
+Reviewed-by: Stefan Klug <stefan.klug@ideasonboard.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..b2803953
+--- /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 particularly sensitive to performance regressions
++therefore 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 spent on processing (collecting statistics
++and debayering) only, it does not measure the time spent 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:: text
++
++   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:: shell
++
++   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:: shell
++
++   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.5 W 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. 13 W while running qcam versus
++approx. 4-5 W while setting idle with its OLED panel on.
+-- 
+2.43.2
+
diff --git a/users/flokli/ipu6-softisp/libcamera/0018-libcamera-software_isp-Apply-black-level-compensatio.patch b/users/flokli/ipu6-softisp/libcamera/0018-libcamera-software_isp-Apply-black-level-compensatio.patch
new file mode 100644
index 0000000000..c746b74dba
--- /dev/null
+++ b/users/flokli/ipu6-softisp/libcamera/0018-libcamera-software_isp-Apply-black-level-compensatio.patch
@@ -0,0 +1,396 @@
+From bb608d177135d74e3c98b8a61fb459ebe254bca5 Mon Sep 17 00:00:00 2001
+From: Milan Zamazal <mzamazal@redhat.com>
+Date: Mon, 11 Mar 2024 15:15:21 +0100
+Subject: [PATCH 18/21] libcamera: software_isp: Apply black level compensation
+
+Black may not be represented as 0 pixel value for given hardware, it may be
+higher.  If this is not compensated then various problems may occur such as low
+contrast or suboptimal exposure.
+
+The black pixel value can be either retrieved from a tuning file for the given
+hardware, or automatically on fly.  The former is the right and correct method,
+while the latter can be used when a tuning file is not available for the given
+hardware.  Since there is currently no support for tuning files in software ISP,
+the automatic, hardware independent way, is always used.  Support for tuning
+files should be added in future but it will require more work than this patch.
+
+The patch looks at the image histogram and assumes that black starts when pixel
+values start occurring on the left.  A certain amount of the darkest pixels is
+ignored; it doesn't matter whether they represent various kinds of noise or are
+real, they are better to omit in any case to make the image looking better.  It
+also doesn't matter whether the darkest pixels occur around the supposed black
+level or are spread between 0 and the black level, the difference is not
+important.
+
+An arbitrary threshold of 2% darkest pixels is applied; there is no magic about
+that value.
+
+The patch assumes that the black values for different colors are the same and
+doesn't attempt any other non-primitive enhancements.  It cannot completely
+replace tuning files and simplicity, while providing visible benefit, is its
+goal.  Anything more sophisticated is left for future patches.
+
+A possible cheap enhancement, if needed, could be setting exposure + gain to
+minimum values temporarily, before setting the black level.  In theory, the
+black level should be fixed but it may not be reached in all images.  For this
+reason, the patch updates black level only if the observed value is lower than
+the current one; it should be never increased.
+
+The purpose of the patch is to compensate for hardware properties.  General
+image contrast enhancements are out of scope of this patch.
+
+Stats are still gathered as an uncorrected histogram, to avoid any confusion and
+to represent the raw image data.  Exposure must be determined after the black
+level correction -- it has no influence on the sub-black area and must be
+correct after applying the black level correction.  The granularity of the
+histogram is increased from 16 to 64 to provide a better precision (there is no
+theory behind either of those numbers).
+
+Reviewed-by: Hans de Goede <hdegoede@redhat.com>
+Signed-off-by: Milan Zamazal <mzamazal@redhat.com>
+Signed-off-by: Hans de Goede <hdegoede@redhat.com>
+---
+ .../internal/software_isp/debayer_params.h    |  4 +
+ .../internal/software_isp/swisp_stats.h       | 10 ++-
+ src/ipa/simple/black_level.cpp                | 86 +++++++++++++++++++
+ src/ipa/simple/black_level.h                  | 28 ++++++
+ src/ipa/simple/meson.build                    |  7 +-
+ src/ipa/simple/soft_simple.cpp                | 28 ++++--
+ src/libcamera/software_isp/debayer_cpu.cpp    | 13 ++-
+ src/libcamera/software_isp/debayer_cpu.h      |  1 +
+ src/libcamera/software_isp/software_isp.cpp   |  2 +-
+ 9 files changed, 162 insertions(+), 17 deletions(-)
+ create mode 100644 src/ipa/simple/black_level.cpp
+ create mode 100644 src/ipa/simple/black_level.h
+
+diff --git a/include/libcamera/internal/software_isp/debayer_params.h b/include/libcamera/internal/software_isp/debayer_params.h
+index 98965fa1..5e38e08b 100644
+--- a/include/libcamera/internal/software_isp/debayer_params.h
++++ b/include/libcamera/internal/software_isp/debayer_params.h
+@@ -43,6 +43,10 @@ struct DebayerParams {
+ 	 * \brief Gamma correction, 1.0 is no correction
+ 	 */
+ 	float gamma;
++	/**
++	 * \brief Level of the black point, 0..255, 0 is no correction.
++	 */
++	unsigned int blackLevel;
+ };
+ 
+ } /* namespace libcamera */
+diff --git a/include/libcamera/internal/software_isp/swisp_stats.h b/include/libcamera/internal/software_isp/swisp_stats.h
+index afe42c9a..25cd5abd 100644
+--- a/include/libcamera/internal/software_isp/swisp_stats.h
++++ b/include/libcamera/internal/software_isp/swisp_stats.h
+@@ -7,6 +7,8 @@
+ 
+ #pragma once
+ 
++#include <array>
++
+ namespace libcamera {
+ 
+ /**
+@@ -28,11 +30,15 @@ struct SwIspStats {
+ 	/**
+ 	 * \brief Number of bins in the yHistogram.
+ 	 */
+-	static constexpr unsigned int kYHistogramSize = 16;
++	static constexpr unsigned int kYHistogramSize = 64;
++	/**
++	 * \brief Type of the histogram.
++	 */
++	using histogram = std::array<unsigned int, kYHistogramSize>;
+ 	/**
+ 	 * \brief A histogram of luminance values.
+ 	 */
+-	std::array<unsigned int, kYHistogramSize> yHistogram;
++	histogram yHistogram;
+ };
+ 
+ } /* namespace libcamera */
+diff --git a/src/ipa/simple/black_level.cpp b/src/ipa/simple/black_level.cpp
+new file mode 100644
+index 00000000..8d52201b
+--- /dev/null
++++ b/src/ipa/simple/black_level.cpp
+@@ -0,0 +1,86 @@
++/* SPDX-License-Identifier: LGPL-2.1-or-later */
++/*
++ * Copyright (C) 2024, Red Hat Inc.
++ *
++ * black_level.cpp - black level handling
++ */
++
++#include "black_level.h"
++
++#include <numeric>
++
++#include <libcamera/base/log.h>
++
++namespace libcamera {
++
++LOG_DEFINE_CATEGORY(IPASoftBL)
++
++/**
++ * \class BlackLevel
++ * \brief Object providing black point level for software ISP
++ *
++ * Black level can be provided in hardware tuning files or, if no tuning file is
++ * available for the given hardware, guessed automatically, with less accuracy.
++ * As tuning files are not yet implemented for software ISP, BlackLevel
++ * currently provides only guessed black levels.
++ *
++ * This class serves for tracking black level as a property of the underlying
++ * hardware, not as means of enhancing a particular scene or image.
++ *
++ * The class is supposed to be instantiated for the given camera stream.
++ * The black level can be retrieved using BlackLevel::get() method. It is
++ * initially 0 and may change when updated using BlackLevel::update() method.
++ */
++
++BlackLevel::BlackLevel()
++	: blackLevel_(255), blackLevelSet_(false)
++{
++}
++
++/**
++ * \brief Return the current black level
++ *
++ * \return The black level, in the range from 0 (minimum) to 255 (maximum).
++ * If the black level couldn't be determined yet, return 0.
++ */
++unsigned int BlackLevel::get() const
++{
++	return blackLevelSet_ ? blackLevel_ : 0;
++}
++
++/**
++ * \brief Update black level from the provided histogram
++ * \param[in] yHistogram The histogram to be used for updating black level
++ *
++ * The black level is property of the given hardware, not image. It is updated
++ * only if it has not been yet set or if it is lower than the lowest value seen
++ * so far.
++ */
++void BlackLevel::update(SwIspStats::histogram &yHistogram)
++{
++	// The constant is selected to be "good enough", not overly conservative or
++	// aggressive. There is no magic about the given value.
++	constexpr float ignoredPercentage_ = 0.02;
++	const unsigned int total =
++		std::accumulate(begin(yHistogram), end(yHistogram), 0);
++	const unsigned int pixelThreshold = ignoredPercentage_ * total;
++	const unsigned int currentBlackIdx =
++		blackLevel_ / (256 / SwIspStats::kYHistogramSize);
++
++	for (unsigned int i = 0, seen = 0;
++	     i < currentBlackIdx && i < SwIspStats::kYHistogramSize;
++	     i++) {
++		seen += yHistogram[i];
++		if (seen >= pixelThreshold) {
++			blackLevel_ = i * (256 / SwIspStats::kYHistogramSize);
++			blackLevelSet_ = true;
++			LOG(IPASoftBL, Debug)
++				<< "Auto-set black level: "
++				<< i << "/" << SwIspStats::kYHistogramSize
++				<< " (" << 100 * (seen - yHistogram[i]) / total << "% below, "
++				<< 100 * seen / total << "% at or below)";
++			break;
++		}
++	};
++}
++} // namespace libcamera
+diff --git a/src/ipa/simple/black_level.h b/src/ipa/simple/black_level.h
+new file mode 100644
+index 00000000..b3785db0
+--- /dev/null
++++ b/src/ipa/simple/black_level.h
+@@ -0,0 +1,28 @@
++/* SPDX-License-Identifier: LGPL-2.1-or-later */
++/*
++ * Copyright (C) 2024, Red Hat Inc.
++ *
++ * black_level.h - black level handling
++ */
++
++#pragma once
++
++#include <array>
++
++#include "libcamera/internal/software_isp/swisp_stats.h"
++
++namespace libcamera {
++
++class BlackLevel
++{
++public:
++	BlackLevel();
++	unsigned int get() const;
++	void update(std::array<unsigned int, SwIspStats::kYHistogramSize> &yHistogram);
++
++private:
++	unsigned int blackLevel_;
++	bool blackLevelSet_;
++};
++
++} // namespace libcamera
+diff --git a/src/ipa/simple/meson.build b/src/ipa/simple/meson.build
+index 3e863db7..44b5f1d7 100644
+--- a/src/ipa/simple/meson.build
++++ b/src/ipa/simple/meson.build
+@@ -2,8 +2,13 @@
+ 
+ ipa_name = 'ipa_soft_simple'
+ 
++soft_simple_sources = files([
++    'soft_simple.cpp',
++    'black_level.cpp',
++])
++
+ mod = shared_module(ipa_name,
+-                    ['soft_simple.cpp', libcamera_generated_ipa_headers],
++                    [soft_simple_sources, libcamera_generated_ipa_headers],
+                     name_prefix : '',
+                     include_directories : [ipa_includes, libipa_includes],
+                     dependencies : libcamera_private,
+diff --git a/src/ipa/simple/soft_simple.cpp b/src/ipa/simple/soft_simple.cpp
+index 312df4ba..ac027568 100644
+--- a/src/ipa/simple/soft_simple.cpp
++++ b/src/ipa/simple/soft_simple.cpp
+@@ -22,6 +22,8 @@
+ #include "libcamera/internal/software_isp/debayer_params.h"
+ #include "libcamera/internal/software_isp/swisp_stats.h"
+ 
++#include "black_level.h"
++
+ namespace libcamera {
+ 
+ LOG_DEFINE_CATEGORY(IPASoft)
+@@ -33,7 +35,8 @@ class IPASoftSimple : public ipa::soft::IPASoftInterface
+ public:
+ 	IPASoftSimple()
+ 		: params_(static_cast<DebayerParams *>(MAP_FAILED)),
+-		  stats_(static_cast<SwIspStats *>(MAP_FAILED)), ignore_updates_(0)
++		  stats_(static_cast<SwIspStats *>(MAP_FAILED)),
++		  blackLevel_(BlackLevel()), ignore_updates_(0)
+ 	{
+ 	}
+ 
+@@ -63,6 +66,7 @@ private:
+ 	SharedFD fdParams_;
+ 	DebayerParams *params_;
+ 	SwIspStats *stats_;
++	BlackLevel blackLevel_;
+ 
+ 	int32_t exposure_min_, exposure_max_;
+ 	int32_t again_min_, again_max_;
+@@ -196,6 +200,10 @@ void IPASoftSimple::processStats(const ControlList &sensorControls)
+ 	params_->gainG = 256;
+ 	params_->gamma = 0.5;
+ 
++	if (ignore_updates_ > 0)
++		blackLevel_.update(stats_->yHistogram);
++	params_->blackLevel = blackLevel_.get();
++
+ 	setIspParams.emit(0);
+ 
+ 	/*
+@@ -211,18 +219,19 @@ void IPASoftSimple::processStats(const ControlList &sensorControls)
+ 	 * Calculate Mean Sample Value (MSV) according to formula from:
+ 	 * https://www.araa.asn.au/acra/acra2007/papers/paper84final.pdf
+ 	 */
+-	constexpr unsigned int yHistValsPerBin =
+-		SwIspStats::kYHistogramSize / kExposureBinsCount;
+-	constexpr unsigned int yHistValsPerBinMod =
+-		SwIspStats::kYHistogramSize /
+-		(SwIspStats::kYHistogramSize % kExposureBinsCount + 1);
++	const unsigned int blackLevelHistIdx =
++		params_->blackLevel / (256 / SwIspStats::kYHistogramSize);
++	const unsigned int histogramSize = SwIspStats::kYHistogramSize - blackLevelHistIdx;
++	const unsigned int yHistValsPerBin = histogramSize / kExposureBinsCount;
++	const unsigned int yHistValsPerBinMod =
++		histogramSize / (histogramSize % kExposureBinsCount + 1);
+ 	int ExposureBins[kExposureBinsCount] = {};
+ 	unsigned int denom = 0;
+ 	unsigned int num = 0;
+ 
+-	for (unsigned int i = 0; i < SwIspStats::kYHistogramSize; i++) {
++	for (unsigned int i = 0; i < histogramSize; i++) {
+ 		unsigned int idx = (i - (i / yHistValsPerBinMod)) / yHistValsPerBin;
+-		ExposureBins[idx] += stats_->yHistogram[i];
++		ExposureBins[idx] += stats_->yHistogram[blackLevelHistIdx + i];
+ 	}
+ 
+ 	for (unsigned int i = 0; i < kExposureBinsCount; i++) {
+@@ -256,7 +265,8 @@ void IPASoftSimple::processStats(const ControlList &sensorControls)
+ 
+ 	LOG(IPASoft, Debug) << "exposureMSV " << exposureMSV
+ 			    << " exp " << exposure_ << " again " << again_
+-			    << " gain R/B " << params_->gainR << "/" << params_->gainB;
++			    << " gain R/B " << params_->gainR << "/" << params_->gainB
++			    << " black level " << params_->blackLevel;
+ }
+ 
+ void IPASoftSimple::updateExposure(double exposureMSV)
+diff --git a/src/libcamera/software_isp/debayer_cpu.cpp b/src/libcamera/software_isp/debayer_cpu.cpp
+index a1692693..3be3cdfe 100644
+--- a/src/libcamera/software_isp/debayer_cpu.cpp
++++ b/src/libcamera/software_isp/debayer_cpu.cpp
+@@ -35,7 +35,7 @@ namespace libcamera {
+  * \param[in] stats Pointer to the stats object to use.
+  */
+ DebayerCpu::DebayerCpu(std::unique_ptr<SwStatsCpu> stats)
+-	: stats_(std::move(stats)), gamma_correction_(1.0)
++	: stats_(std::move(stats)), gamma_correction_(1.0), blackLevel_(0)
+ {
+ #ifdef __x86_64__
+ 	enableInputMemcpy_ = false;
+@@ -683,11 +683,16 @@ void DebayerCpu::process(FrameBuffer *input, FrameBuffer *output, DebayerParams
+ 	}
+ 
+ 	/* Apply DebayerParams */
+-	if (params.gamma != gamma_correction_) {
+-		for (unsigned int i = 0; i < kGammaLookupSize; i++)
+-			gamma_[i] = UINT8_MAX * powf(i / (kGammaLookupSize - 1.0), params.gamma);
++	if (params.gamma != gamma_correction_ || params.blackLevel != blackLevel_) {
++		const unsigned int blackIndex =
++			params.blackLevel * kGammaLookupSize / 256;
++		std::fill(gamma_.begin(), gamma_.begin() + blackIndex, 0);
++		const float divisor = kGammaLookupSize - blackIndex - 1.0;
++		for (unsigned int i = blackIndex; i < kGammaLookupSize; i++)
++			gamma_[i] = UINT8_MAX * powf((i - blackIndex) / divisor, params.gamma);
+ 
+ 		gamma_correction_ = params.gamma;
++		blackLevel_ = params.blackLevel;
+ 	}
+ 
+ 	if (swapRedBlueGains_)
+diff --git a/src/libcamera/software_isp/debayer_cpu.h b/src/libcamera/software_isp/debayer_cpu.h
+index 5f44fc65..ea02f909 100644
+--- a/src/libcamera/software_isp/debayer_cpu.h
++++ b/src/libcamera/software_isp/debayer_cpu.h
+@@ -147,6 +147,7 @@ private:
+ 	bool enableInputMemcpy_;
+ 	bool swapRedBlueGains_;
+ 	float gamma_correction_;
++	unsigned int blackLevel_;
+ 	unsigned int measuredFrames_;
+ 	int64_t frameProcessTime_;
+ 	/* Skip 30 frames for things to stabilize then measure 30 frames */
+diff --git a/src/libcamera/software_isp/software_isp.cpp b/src/libcamera/software_isp/software_isp.cpp
+index 388b4496..9b49be41 100644
+--- a/src/libcamera/software_isp/software_isp.cpp
++++ b/src/libcamera/software_isp/software_isp.cpp
+@@ -64,7 +64,7 @@ LOG_DEFINE_CATEGORY(SoftwareIsp)
+  */
+ SoftwareIsp::SoftwareIsp(PipelineHandler *pipe, const ControlInfoMap &sensorControls)
+ 	: debayer_(nullptr),
+-	  debayerParams_{ DebayerParams::kGain10, DebayerParams::kGain10, DebayerParams::kGain10, 0.5f },
++	  debayerParams_{ DebayerParams::kGain10, DebayerParams::kGain10, DebayerParams::kGain10, 0.5f, 0 },
+ 	  dmaHeap_(DmaHeap::DmaHeapFlag::Cma | DmaHeap::DmaHeapFlag::System)
+ {
+ 	if (!dmaHeap_.isValid()) {
+-- 
+2.43.2
+
diff --git a/users/flokli/ipu6-softisp/libcamera/0019-libcamera-Soft-IPA-use-CameraSensorHelper-for-analog.patch b/users/flokli/ipu6-softisp/libcamera/0019-libcamera-Soft-IPA-use-CameraSensorHelper-for-analog.patch
new file mode 100644
index 0000000000..5b562c603c
--- /dev/null
+++ b/users/flokli/ipu6-softisp/libcamera/0019-libcamera-Soft-IPA-use-CameraSensorHelper-for-analog.patch
@@ -0,0 +1,239 @@
+From b0c07674abecb05dc0af93a4b749971f057bc3c6 Mon Sep 17 00:00:00 2001
+From: Andrei Konovalov <andrey.konovalov.ynk@gmail.com>
+Date: Mon, 11 Mar 2024 15:15:22 +0100
+Subject: [PATCH 19/21] libcamera: Soft IPA: use CameraSensorHelper for
+ analogue gain
+
+Use CameraSensorHelper to convert the analogue gain code read from the
+camera sensor into real analogue gain value. In the future this makes
+it possible to use faster AE/AGC algorithm. For now the same AE/AGC
+algorithm is used, but even then the CameraSensorHelper lets us use the
+full range of analogue gain values.
+
+If there is no CameraSensorHelper for the camera sensor in use, a
+warning log message is printed, and the AE/AGC works exactly as before
+this change.
+
+Signed-off-by: Andrei Konovalov <andrey.konovalov.ynk@gmail.com>
+Reviewed-by: Hans de Goede <hdegoede@redhat.com>
+Signed-off-by: Hans de Goede <hdegoede@redhat.com>
+Reviewed-by: Milan Zamazal <mzamazal@redhat.com>
+---
+ .../internal/software_isp/software_isp.h      |  3 +-
+ src/ipa/simple/soft_simple.cpp                | 77 ++++++++++++-------
+ src/libcamera/pipeline/simple/simple.cpp      |  2 +-
+ src/libcamera/software_isp/software_isp.cpp   |  8 +-
+ 4 files changed, 57 insertions(+), 33 deletions(-)
+
+diff --git a/include/libcamera/internal/software_isp/software_isp.h b/include/libcamera/internal/software_isp/software_isp.h
+index 8d25e979..2a6db7ba 100644
+--- a/include/libcamera/internal/software_isp/software_isp.h
++++ b/include/libcamera/internal/software_isp/software_isp.h
+@@ -26,6 +26,7 @@
+ #include <libcamera/ipa/soft_ipa_interface.h>
+ #include <libcamera/ipa/soft_ipa_proxy.h>
+ 
++#include "libcamera/internal/camera_sensor.h"
+ #include "libcamera/internal/dma_heaps.h"
+ #include "libcamera/internal/pipeline_handler.h"
+ #include "libcamera/internal/shared_mem_object.h"
+@@ -43,7 +44,7 @@ LOG_DECLARE_CATEGORY(SoftwareIsp)
+ class SoftwareIsp
+ {
+ public:
+-	SoftwareIsp(PipelineHandler *pipe, const ControlInfoMap &sensorControls);
++	SoftwareIsp(PipelineHandler *pipe, const CameraSensor *sensor);
+ 	~SoftwareIsp();
+ 
+ 	int loadConfiguration([[maybe_unused]] const std::string &filename) { return 0; }
+diff --git a/src/ipa/simple/soft_simple.cpp b/src/ipa/simple/soft_simple.cpp
+index ac027568..e4d64762 100644
+--- a/src/ipa/simple/soft_simple.cpp
++++ b/src/ipa/simple/soft_simple.cpp
+@@ -22,6 +22,8 @@
+ #include "libcamera/internal/software_isp/debayer_params.h"
+ #include "libcamera/internal/software_isp/swisp_stats.h"
+ 
++#include "libipa/camera_sensor_helper.h"
++
+ #include "black_level.h"
+ 
+ namespace libcamera {
+@@ -67,18 +69,27 @@ private:
+ 	DebayerParams *params_;
+ 	SwIspStats *stats_;
+ 	BlackLevel blackLevel_;
++	std::unique_ptr<CameraSensorHelper> camHelper_;
+ 
+ 	int32_t exposure_min_, exposure_max_;
+-	int32_t again_min_, again_max_;
+-	int32_t again_, exposure_;
++	int32_t exposure_;
++	double again_min_, again_max_, againMinStep_;
++	double again_;
+ 	unsigned int ignore_updates_;
+ };
+ 
+-int IPASoftSimple::init([[maybe_unused]] const IPASettings &settings,
++int IPASoftSimple::init(const IPASettings &settings,
+ 			const SharedFD &fdStats,
+ 			const SharedFD &fdParams,
+ 			const ControlInfoMap &sensorInfoMap)
+ {
++	camHelper_ = CameraSensorHelperFactoryBase::create(settings.sensorModel);
++	if (camHelper_ == nullptr) {
++		LOG(IPASoft, Warning)
++			<< "Failed to create camera sensor helper for "
++			<< settings.sensorModel;
++	}
++
+ 	fdStats_ = fdStats;
+ 	if (!fdStats_.isValid()) {
+ 		LOG(IPASoft, Error) << "Invalid Statistics handle";
+@@ -132,25 +143,35 @@ int IPASoftSimple::configure(const ControlInfoMap &sensorInfoMap)
+ 		exposure_min_ = 1;
+ 	}
+ 
+-	again_min_ = gain_info.min().get<int32_t>();
+-	again_max_ = gain_info.max().get<int32_t>();
+-	/*
+-	 * The camera sensor gain (g) is usually not equal to the value written
+-	 * into the gain register (x). But the way how the AGC algorithm changes
+-	 * the gain value to make the total exposure closer to the optimum assumes
+-	 * that g(x) is not too far from linear function. If the minimal gain is 0,
+-	 * the g(x) is likely to be far from the linear, like g(x) = a / (b * x + c).
+-	 * To avoid unexpected changes to the gain by the AGC algorithm (abrupt near
+-	 * one edge, and very small near the other) we limit the range of the gain
+-	 * values used.
+-	 */
+-	if (!again_min_) {
+-		LOG(IPASoft, Warning) << "Minimum gain is zero, that can't be linear";
+-		again_min_ = std::min(100, again_min_ / 2 + again_max_ / 2);
++	int32_t again_min = gain_info.min().get<int32_t>();
++	int32_t again_max = gain_info.max().get<int32_t>();
++
++	if (camHelper_) {
++		again_min_ = camHelper_->gain(again_min);
++		again_max_ = camHelper_->gain(again_max);
++		againMinStep_ = (again_max_ - again_min_) / 100.0;
++	} else {
++		/*
++		 * The camera sensor gain (g) is usually not equal to the value written
++		 * into the gain register (x). But the way how the AGC algorithm changes
++		 * the gain value to make the total exposure closer to the optimum assumes
++		 * that g(x) is not too far from linear function. If the minimal gain is 0,
++		 * the g(x) is likely to be far from the linear, like g(x) = a / (b * x + c).
++		 * To avoid unexpected changes to the gain by the AGC algorithm (abrupt near
++		 * one edge, and very small near the other) we limit the range of the gain
++		 * values used.
++		 */
++		again_max_ = again_max;
++		if (!again_min) {
++			LOG(IPASoft, Warning) << "Minimum gain is zero, that can't be linear";
++			again_min_ = std::min(100, again_min / 2 + again_max / 2);
++		}
++		againMinStep_ = 1.0;
+ 	}
+ 
+ 	LOG(IPASoft, Info) << "Exposure " << exposure_min_ << "-" << exposure_max_
+-			   << ", gain " << again_min_ << "-" << again_max_;
++			   << ", gain " << again_min_ << "-" << again_max_
++			   << " (" << againMinStep_ << ")";
+ 
+ 	return 0;
+ }
+@@ -252,12 +273,14 @@ void IPASoftSimple::processStats(const ControlList &sensorControls)
+ 	ControlList ctrls(sensorControls);
+ 
+ 	exposure_ = ctrls.get(V4L2_CID_EXPOSURE).get<int32_t>();
+-	again_ = ctrls.get(V4L2_CID_ANALOGUE_GAIN).get<int32_t>();
++	int32_t again = ctrls.get(V4L2_CID_ANALOGUE_GAIN).get<int32_t>();
++	again_ = camHelper_ ? camHelper_->gain(again) : again;
+ 
+ 	updateExposure(exposureMSV);
+ 
+ 	ctrls.set(V4L2_CID_EXPOSURE, exposure_);
+-	ctrls.set(V4L2_CID_ANALOGUE_GAIN, again_);
++	ctrls.set(V4L2_CID_ANALOGUE_GAIN,
++		  static_cast<int32_t>(camHelper_ ? camHelper_->gainCode(again_) : again_));
+ 
+ 	ignore_updates_ = 2;
+ 
+@@ -276,7 +299,7 @@ void IPASoftSimple::updateExposure(double exposureMSV)
+ 	static constexpr uint8_t kExpNumeratorUp = kExpDenominator + 1;
+ 	static constexpr uint8_t kExpNumeratorDown = kExpDenominator - 1;
+ 
+-	int next;
++	double next;
+ 
+ 	if (exposureMSV < kExposureOptimal - kExposureSatisfactory) {
+ 		next = exposure_ * kExpNumeratorUp / kExpDenominator;
+@@ -286,18 +309,18 @@ void IPASoftSimple::updateExposure(double exposureMSV)
+ 			exposure_ = next;
+ 		if (exposure_ >= exposure_max_) {
+ 			next = again_ * kExpNumeratorUp / kExpDenominator;
+-			if (next - again_ < 1)
+-				again_ += 1;
++			if (next - again_ < againMinStep_)
++				again_ += againMinStep_;
+ 			else
+ 				again_ = next;
+ 		}
+ 	}
+ 
+ 	if (exposureMSV > kExposureOptimal + kExposureSatisfactory) {
+-		if (exposure_ == exposure_max_ && again_ != again_min_) {
++		if (exposure_ == exposure_max_ && again_ > again_min_) {
+ 			next = again_ * kExpNumeratorDown / kExpDenominator;
+-			if (again_ - next < 1)
+-				again_ -= 1;
++			if (again_ - next < againMinStep_)
++				again_ -= againMinStep_;
+ 			else
+ 				again_ = next;
+ 		} else {
+diff --git a/src/libcamera/pipeline/simple/simple.cpp b/src/libcamera/pipeline/simple/simple.cpp
+index c3ebb7b7..7e932a14 100644
+--- a/src/libcamera/pipeline/simple/simple.cpp
++++ b/src/libcamera/pipeline/simple/simple.cpp
+@@ -525,7 +525,7 @@ int SimpleCameraData::init()
+ 	 * Instantiate Soft ISP if this is enabled for the given driver and no converter is used.
+ 	 */
+ 	if (!converter_ && pipe->swIspEnabled()) {
+-		swIsp_ = std::make_unique<SoftwareIsp>(pipe, sensor_->controls());
++		swIsp_ = std::make_unique<SoftwareIsp>(pipe, sensor_.get());
+ 		if (!swIsp_->isValid()) {
+ 			LOG(SimplePipeline, Warning)
+ 				<< "Failed to create software ISP, disabling software debayering";
+diff --git a/src/libcamera/software_isp/software_isp.cpp b/src/libcamera/software_isp/software_isp.cpp
+index 9b49be41..ea4d96e4 100644
+--- a/src/libcamera/software_isp/software_isp.cpp
++++ b/src/libcamera/software_isp/software_isp.cpp
+@@ -60,9 +60,9 @@ LOG_DEFINE_CATEGORY(SoftwareIsp)
+ /**
+  * \brief Constructs SoftwareIsp object
+  * \param[in] pipe The pipeline handler in use
+- * \param[in] sensorControls ControlInfoMap describing the controls supported by the sensor
++ * \param[in] sensor Pointer to the CameraSensor instance owned by the pipeline handler
+  */
+-SoftwareIsp::SoftwareIsp(PipelineHandler *pipe, const ControlInfoMap &sensorControls)
++SoftwareIsp::SoftwareIsp(PipelineHandler *pipe, const CameraSensor *sensor)
+ 	: debayer_(nullptr),
+ 	  debayerParams_{ DebayerParams::kGain10, DebayerParams::kGain10, DebayerParams::kGain10, 0.5f, 0 },
+ 	  dmaHeap_(DmaHeap::DmaHeapFlag::Cma | DmaHeap::DmaHeapFlag::System)
+@@ -97,10 +97,10 @@ SoftwareIsp::SoftwareIsp(PipelineHandler *pipe, const ControlInfoMap &sensorCont
+ 		return;
+ 	}
+ 
+-	int ret = ipa_->init(IPASettings{ "No cfg file", "No sensor model" },
++	int ret = ipa_->init(IPASettings{ "No cfg file", sensor->model() },
+ 			     debayer_->getStatsFD(),
+ 			     sharedParams_.fd(),
+-			     sensorControls);
++			     sensor->controls());
+ 	if (ret) {
+ 		LOG(SoftwareIsp, Error) << "IPA init failed";
+ 		debayer_.reset();
+-- 
+2.43.2
+
diff --git a/users/flokli/ipu6-softisp/libcamera/0020-ov01a1s-HACK.patch b/users/flokli/ipu6-softisp/libcamera/0020-ov01a1s-HACK.patch
new file mode 100644
index 0000000000..343f04c850
--- /dev/null
+++ b/users/flokli/ipu6-softisp/libcamera/0020-ov01a1s-HACK.patch
@@ -0,0 +1,95 @@
+From 2bde6e420571c6dc0ff25246620b4c987987f6be 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 20/21] 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 | 4 ++++
+ 3 files changed, 18 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 3be3cdfe..d6599805 100644
+--- a/src/libcamera/software_isp/debayer_cpu.cpp
++++ b/src/libcamera/software_isp/debayer_cpu.cpp
+@@ -23,6 +23,7 @@
+ 
+ namespace libcamera {
+ 
++extern bool is_ov01a1s;
+ /**
+  * \class DebayerCpu
+  * \brief Class for debayering on the CPU
+@@ -262,6 +263,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)) {
+@@ -330,7 +334,11 @@ int DebayerCpu::setDebayerFunctions(PixelFormat inputFormat, PixelFormat outputF
+ 	BayerFormat bayerFormat =
+ 		BayerFormat::fromPixelFormat(inputFormat);
+ 
++	if (is_ov01a1s)
++		bayerFormat.order = BayerFormat::IGIG_GBGR_IGIG_GRGB;
++
+ 	xShift_ = 0;
++
+ 	swapRedBlueGains_ = false;
+ 
+ 	switch (outputFormat) {
+diff --git a/src/libcamera/software_isp/swstats_cpu.cpp b/src/libcamera/software_isp/swstats_cpu.cpp
+index be310f56..cda1894a 100644
+--- a/src/libcamera/software_isp/swstats_cpu.cpp
++++ b/src/libcamera/software_isp/swstats_cpu.cpp
+@@ -19,6 +19,7 @@
+ 
+ namespace libcamera {
+ 
++extern bool is_ov01a1s;
+ /**
+  * \class SwStatsCpu
+  * \brief Class for gathering statistics on the CPU
+@@ -271,6 +272,9 @@ int SwStatsCpu::configure(const StreamConfiguration &inputCfg)
+ 	BayerFormat bayerFormat =
+ 		BayerFormat::fromPixelFormat(inputCfg.pixelFormat);
+ 
++	if (is_ov01a1s)
++		bayerFormat.order = BayerFormat::IGIG_GBGR_IGIG_GRGB;
++
+ 	if (bayerFormat.packing == BayerFormat::Packing::None &&
+ 	    setupStandardBayerOrder(bayerFormat.order) == 0) {
+ 		switch (bayerFormat.bitDepth) {
+-- 
+2.43.2
+
diff --git a/users/flokli/ipu6-softisp/libcamera/0021-libcamera-debayer_cpu-Make-the-minimum-size-1280x720.patch b/users/flokli/ipu6-softisp/libcamera/0021-libcamera-debayer_cpu-Make-the-minimum-size-1280x720.patch
new file mode 100644
index 0000000000..a3af38c93c
--- /dev/null
+++ b/users/flokli/ipu6-softisp/libcamera/0021-libcamera-debayer_cpu-Make-the-minimum-size-1280x720.patch
@@ -0,0 +1,42 @@
+From a21bb26dcfcc00425f031421b87576f9c81e4824 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 21/21] 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>
+---
+ src/libcamera/software_isp/debayer_cpu.cpp | 15 +++++++++++----
+ 1 file changed, 11 insertions(+), 4 deletions(-)
+
+diff --git a/src/libcamera/software_isp/debayer_cpu.cpp b/src/libcamera/software_isp/debayer_cpu.cpp
+index d6599805..5a06b191 100644
+--- a/src/libcamera/software_isp/debayer_cpu.cpp
++++ b/src/libcamera/software_isp/debayer_cpu.cpp
+@@ -790,10 +790,17 @@ SizeRange DebayerCpu::sizes(PixelFormat inputFormat, const Size &inputSize)
+ 		return {};
+ 	}
+ 
+-	return SizeRange(Size(pattern_size.width, pattern_size.height),
+-			 Size((inputSize.width - 2 * pattern_size.width) & ~(pattern_size.width - 1),
+-			      (inputSize.height - 2 * border_height) & ~(pattern_size.height - 1)),
+-			 pattern_size.width, pattern_size.height);
++	/*
++	 * 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);
+ }
+ 
+ } /* namespace libcamera */
+-- 
+2.43.2
+
diff --git a/users/flokli/keyboards/dilemma/default.nix b/users/flokli/keyboards/dilemma/default.nix
new file mode 100644
index 0000000000..cd05b288e8
--- /dev/null
+++ b/users/flokli/keyboards/dilemma/default.nix
@@ -0,0 +1,47 @@
+{ depot, pkgs, ... }:
+
+rec {
+  qmk_firmware_src = pkgs.fetchFromGitHub {
+    owner = "qmk";
+    repo = "qmk_firmware";
+    rev = "0.24.8";
+    hash = "sha256-DRHPfJXF1KF1+EwkbeGhqhVrpfp21JY2spOZxesZFbA=";
+    fetchSubmodules = true;
+  };
+
+  firmware = pkgs.stdenv.mkDerivation {
+    name = "keychron-bastardkb-dilemma-firmware";
+
+    src = qmk_firmware_src;
+
+    patches = [ ./enable-taps.patch ];
+
+    postPatch = ''
+      patchShebangs util/uf2conv.py
+    '';
+
+    nativeBuildInputs = [
+      pkgs.python3
+      pkgs.qmk
+    ];
+
+    buildPhase = ''
+      mkdir -p keyboards/bastardkb/dilemma/3x5_3/keymaps/flokli
+      cp ${./keymap.c} keyboards/bastardkb/dilemma/3x5_3/keymaps/flokli/keymap.c
+      cp ${./rules.mk} keyboards/bastardkb/dilemma/3x5_3/keymaps/flokli/rules.mk
+
+      make bastardkb/dilemma/3x5_3:flokli
+    '';
+
+    installPhase = ''
+      mkdir -p $out
+      cp bastardkb_dilemma_3x5_3_flokli.uf2 $out/
+    '';
+  };
+
+  flash = pkgs.writeShellScript "flash.sh" ''
+    QMK_HOME=${qmk_firmware_src} ${pkgs.qmk}/bin/qmk flash ${firmware}/bastardkb_dilemma_3x5_3_flokli.uf2
+  '';
+
+  meta.ci.targets = [ "firmware" ];
+}
diff --git a/users/flokli/keyboards/dilemma/enable-taps.patch b/users/flokli/keyboards/dilemma/enable-taps.patch
new file mode 100644
index 0000000000..afded85492
--- /dev/null
+++ b/users/flokli/keyboards/dilemma/enable-taps.patch
@@ -0,0 +1,24 @@
+From 32a1b9a189c13bec03c6b0f258121c47185db0ad Mon Sep 17 00:00:00 2001
+From: Florian Klink <flokli@flokli.de>
+Date: Tue, 23 Jan 2024 11:26:10 +0200
+Subject: [PATCH] bastardkb dilemma: enable taps
+
+---
+ keyboards/bastardkb/dilemma/3x5_3/config.h | 1 +
+ 1 file changed, 1 insertion(+)
+
+diff --git a/keyboards/bastardkb/dilemma/3x5_3/config.h b/keyboards/bastardkb/dilemma/3x5_3/config.h
+index ccbc4e2f58..bf17dc7e02 100644
+--- a/keyboards/bastardkb/dilemma/3x5_3/config.h
++++ b/keyboards/bastardkb/dilemma/3x5_3/config.h
+@@ -42,6 +42,7 @@
+ #define POINTING_DEVICE_CS_PIN GP21
+ #undef CIRQUE_PINNACLE_DIAMETER_MM
+ #define CIRQUE_PINNACLE_DIAMETER_MM 40
++#define CIRQUE_PINNACLE_TAP_ENABLE 1
+ 
+ /* Reset. */
+ #define RP2040_BOOTLOADER_DOUBLE_TAP_RESET
+-- 
+2.43.0
+
diff --git a/users/flokli/keyboards/dilemma/keymap.c b/users/flokli/keyboards/dilemma/keymap.c
new file mode 100644
index 0000000000..2c21ef6c9e
--- /dev/null
+++ b/users/flokli/keyboards/dilemma/keymap.c
@@ -0,0 +1,220 @@
+/**
+ * Copyright 2022 Charly Delay <charly@codesink.dev> (@0xcharly)
+ * Copyright 2023 casuanoob <casuanoob@hotmail.com> (@casuanoob)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include QMK_KEYBOARD_H
+
+enum dilemma_keymap_layers {
+    LAYER_BASE = 0,
+    LAYER_NAVIGATION,
+    LAYER_MOUSE,
+    LAYER_MEDIA,
+    LAYER_NUMERAL,
+    LAYER_SYMBOLS,
+    LAYER_FUNCTION,
+};
+
+// Automatically enable sniping-mode on the pointer layer.
+#define DILEMMA_AUTO_SNIPING_ON_LAYER LAYER_MOUSE
+#define ESC_MED LT(LAYER_MEDIA, KC_ESC)
+#define SPC_NAV LT(LAYER_NAVIGATION, KC_SPC)
+#define TAB_MOU LT(LAYER_MOUSE, KC_TAB)
+#define ENT_SYM LT(LAYER_SYMBOLS, KC_ENT)
+#define BSP_NUM LT(LAYER_NUMERAL, KC_BSPC)
+#define DEL_FUN LT(LAYER_FUNCTION, KC_DEL)
+
+#ifndef POINTING_DEVICE_ENABLE
+#    define DRGSCRL KC_NO
+#    define DPI_MOD KC_NO
+#    define S_D_MOD KC_NO
+#    define SNIPING KC_NO
+#endif // !POINTING_DEVICE_ENABLE
+
+// clang-format off
+/** \brief COLEMAK-DH layout (3 rows, 10 columns). */
+#define LAYOUT_LAYER_BASE                                                                     \
+       KC_Q,    KC_W,    KC_F,    KC_P,    KC_B,    KC_J,    KC_L,    KC_U,    KC_Y, KC_QUOT, \
+       KC_A,    KC_R,    KC_S,    KC_T,    KC_G,    KC_M,    KC_N,    KC_E,    KC_I,    KC_O, \
+       KC_Z,    KC_X,    KC_C,    KC_D,    KC_V,    KC_K,    KC_H, KC_COMMA, KC_DOT, KC_SLSH, \
+                      ESC_MED, SPC_NAV, TAB_MOU, ENT_SYM, BSP_NUM, DEL_FUN
+
+/** Convenience row shorthands. */
+#define _______________DEAD_HALF_ROW_______________ XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX, XXXXXXX
+#define ______________HOME_ROW_GACS_L______________ KC_LGUI, KC_LALT, KC_LCTL, KC_LSFT, XXXXXXX
+#define ______________HOME_ROW_GACS_R______________ XXXXXXX, KC_LSFT, KC_LCTL, KC_RALT, KC_LGUI
+
+/*
+ * Layers used on the Dilemma.
+ *
+ * These layers started off heavily inspired by the Miryoku layout, but trimmed
+ * down and tailored for a stock experience that is meant to be fundation for
+ * further personalization.
+ *
+ * See https://github.com/manna-harbour/miryoku for the original layout.
+ */
+
+/**
+ * \brief Navigation layer.
+ *
+ * Primary left-hand layer (left home thumb) is navigation and editing. Cursor
+ * keys are on the home position, line and page movement below, clipboard
+ * above, caps lock and insert on the inner column. Thumb keys are duplicated
+ * from the base layer to avoid having to layer change mid edit and to enable
+ * auto-repeat.
+*/
+#define LAYOUT_LAYER_NAVIGATION                                                               \
+    _______________DEAD_HALF_ROW_______________, KC_AGAIN,LCTL(KC_V), LCTL(KC_C),  KC_CUT, KC_UNDO, \
+    ______________HOME_ROW_GACS_L______________, KC_CAPS, KC_LEFT,    KC_DOWN,   KC_UP, KC_RGHT, \
+    _______________DEAD_HALF_ROW_______________,  KC_INS, KC_HOME,    KC_PGDN, KC_PGUP,  KC_END, \
+                      XXXXXXX, _______, XXXXXXX,  KC_ENT, KC_BSPC,    KC_DEL
+
+/**
+ * \brief Mouse layer
+ *
+ * Secondary left-hand layer is mouse emulation. Mouse movement mirrors cursor
+ * navigation on home and wheel mirrors line / page movement below. Mouse
+ * buttons are on the thumbs. Left, right, and middle mouse buttons are on the
+ * primary, secondary, and tertiary thumb keys, respectively. Mouse movement,
+ * click, and drag, with modifiers, can be performed from the home position.
+ * Clipboard keys are duplicated from the Nav layer.
+*/
+#define LAYOUT_LAYER_MOUSE                                                                    \
+    _______________DEAD_HALF_ROW_______________, KC_AGAIN,KC_PSTE, KC_COPY,  KC_CUT, KC_UNDO, \
+    ______________HOME_ROW_GACS_L______________, _______, KC_MS_L, KC_MS_D, KC_MS_U, KC_MS_R, \
+    _______________DEAD_HALF_ROW_______________, _______, KC_WH_L, KC_WH_D, KC_WH_U, KC_WH_R, \
+                      XXXXXXX, XXXXXXX, _______, KC_BTN2, KC_BTN1, KC_BTN3
+
+/**
+ * \brief Media layer
+ *
+ * Tertiary left-hand layer is media control, with volume up / volume down and
+ * next / prev mirroring the navigation keys. Pause, stop and mute are on the
+ * primary, secondary, and tertiary thumbs, respectively.
+ *
+ * Keyboard hardware controls are also present, and depend on hardware and
+ * firmware support.
+ *
+ * RGB control is on the top row. RGB Toggle is on the inner index column key.
+ * Combine with Shift for RGB Off. RGB Mode, RGB Hue, RGB Saturation, and RGB
+ * Value are on index, middle, ring, and pinkie column keys, respectively.
+ * Tapping will increase the corresponding value. Combine with Shift to
+ * decrease.
+*/
+#define LAYOUT_LAYER_MEDIA                                                                    \
+    _______________DEAD_HALF_ROW_______________, RGB_TOG, RGB_MOD, RGB_HUI, RGB_SAI, RGB_VAI, \
+    ______________HOME_ROW_GACS_L______________, _______, KC_MPRV, KC_VOLD, KC_VOLU, KC_MNXT, \
+    _______________DEAD_HALF_ROW_______________, _______, _______, _______, _______, _______, \
+                      _______, XXXXXXX, XXXXXXX, KC_MSTP, KC_MPLY, KC_MUTE
+
+/**
+ * \brief Numeral layout.
+ *
+ * Primary right-hand layer (right home thumb) is numerals and symbols. Numerals
+ * are in the standard numpad locations with symbols in the remaining positions.
+ */
+#define LAYOUT_LAYER_NUMERAL                                                                  \
+    KC_LBRC,    KC_7,    KC_8,    KC_9, KC_RBRC, _______________DEAD_HALF_ROW_______________, \
+    KC_SCLN,    KC_4,    KC_5,    KC_6,  KC_EQL, ______________HOME_ROW_GACS_R______________, \
+    KC_GRAVE,   KC_1,    KC_2,    KC_3, KC_BSLS, _______________DEAD_HALF_ROW_______________, \
+                       KC_DOT, KC_0, KC_MINS, XXXXXXX, _______, XXXXXXX
+
+/**
+ * \brief Symbols layer.
+ *
+ * Secondary right-hand layer has shifted symbols in the same locations to reduce
+ * chording when using mods with shifted symbols. `KC_LPRN` is duplicated next to
+ * `KC_RPRN`.
+ */
+#define LAYOUT_LAYER_SYMBOLS                                                                  \
+    KC_LCBR, KC_AMPR, KC_ASTR, KC_LPRN, KC_RCBR, _______________DEAD_HALF_ROW_______________, \
+    KC_COLN,  KC_DLR, KC_PERC, KC_CIRC, KC_PLUS, ______________HOME_ROW_GACS_R______________, \
+    KC_TILD, KC_EXLM,   KC_AT, KC_HASH, KC_PIPE, _______________DEAD_HALF_ROW_______________, \
+                      KC_LPRN, KC_RPRN, KC_UNDS, _______, XXXXXXX, XXXXXXX
+
+/**
+ * \brief Function layer.
+ *
+ * Tertiary right-hand layer has function keys mirroring the numerals on the
+ * primary layer with system keys on the inner column. App is on the tertiary
+ * thumb key and other thumb keys are duplicated from the base layer to enable
+ * auto-repeat.
+ */
+#define LAYOUT_LAYER_FUNCTION                                                                 \
+     KC_F12,   KC_F7,   KC_F8,   KC_F9, KC_PSCR, _______________DEAD_HALF_ROW_______________, \
+     KC_F11,   KC_F4,   KC_F5,   KC_F6, KC_SCRL, ______________HOME_ROW_GACS_R______________, \
+     KC_F10,   KC_F1,   KC_F2,   KC_F3, KC_PAUS, _______________DEAD_HALF_ROW_______________, \
+                      KC_APP, KC_SPC, KC_TAB, XXXXXXX, XXXXXXX, _______
+
+/**
+ * \brief Add Home Row mod to a layout.
+ *
+ * Expects a 10-key per row layout.  Adds support for GACS (Gui, Alt, Ctl, Shift)
+ * home row.  The layout passed in parameter must contain at least 20 keycodes.
+ *
+ * This is meant to be used with `LAYER_BASE` defined above, eg.:
+ *
+ *     HOME_ROW_MOD_GACS(LAYER_BASE)
+ */
+#define _HOME_ROW_MOD_GACS(                                            \
+    L00, L01, L02, L03, L04, R05, R06, R07, R08, R09,                  \
+    L10, L11, L12, L13, L14, R15, R16, R17, R18, R19,                  \
+    ...)                                                               \
+             L00,         L01,         L02,         L03,         L04,  \
+             R05,         R06,         R07,         R08,         R09,  \
+      LGUI_T(L10), LALT_T(L11), LCTL_T(L12), LSFT_T(L13),        L14,  \
+             R15,  RSFT_T(R16), RCTL_T(R17), RALT_T(R18), RGUI_T(R19), \
+      __VA_ARGS__
+#define HOME_ROW_MOD_GACS(...) _HOME_ROW_MOD_GACS(__VA_ARGS__)
+
+
+#define LAYOUT_wrapper(...) LAYOUT_split_3x5_3(__VA_ARGS__)
+
+const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {
+  [LAYER_BASE] = LAYOUT_wrapper(
+    HOME_ROW_MOD_GACS(LAYOUT_LAYER_BASE)
+  ),
+  [LAYER_NAVIGATION] = LAYOUT_wrapper(LAYOUT_LAYER_NAVIGATION),
+  [LAYER_MOUSE] = LAYOUT_wrapper(LAYOUT_LAYER_MOUSE),
+  [LAYER_MEDIA] = LAYOUT_wrapper(LAYOUT_LAYER_MEDIA),
+  [LAYER_NUMERAL] = LAYOUT_wrapper(LAYOUT_LAYER_NUMERAL),
+  [LAYER_SYMBOLS] = LAYOUT_wrapper(LAYOUT_LAYER_SYMBOLS),
+  [LAYER_FUNCTION] = LAYOUT_wrapper(LAYOUT_LAYER_FUNCTION),
+};
+// clang-format on
+
+#ifdef POINTING_DEVICE_ENABLE
+#    ifdef DILEMMA_AUTO_SNIPING_ON_LAYER
+layer_state_t layer_state_set_user(layer_state_t state) {
+    dilemma_set_pointer_sniping_enabled(layer_state_cmp(state, DILEMMA_AUTO_SNIPING_ON_LAYER));
+    return state;
+}
+#    endif // DILEMMA_AUTO_SNIPING_ON_LAYER
+#endif     // POINTING_DEVICE_ENABLE
+
+#ifdef ENCODER_MAP_ENABLE
+// clang-format off
+const uint16_t PROGMEM encoder_map[][NUM_ENCODERS][2] = {
+    [LAYER_BASE]       = {ENCODER_CCW_CW(KC_WH_D, KC_WH_U),  ENCODER_CCW_CW(KC_VOLD, KC_VOLU)},
+    [LAYER_NAVIGATION] = {ENCODER_CCW_CW(KC_PGDN, KC_PGUP),  ENCODER_CCW_CW(KC_VOLU, KC_VOLD)},
+    [LAYER_MOUSE]      = {ENCODER_CCW_CW(RGB_HUD, RGB_HUI),  ENCODER_CCW_CW(RGB_SAD, RGB_SAI)},
+    [LAYER_MEDIA]      = {ENCODER_CCW_CW(KC_PGDN, KC_PGUP),  ENCODER_CCW_CW(KC_VOLU, KC_VOLD)},
+    [LAYER_NUMERAL]    = {ENCODER_CCW_CW(RGB_VAD, RGB_VAI),  ENCODER_CCW_CW(RGB_SPD, RGB_SPI)},
+    [LAYER_SYMBOLS]    = {ENCODER_CCW_CW(RGB_RMOD, RGB_MOD), ENCODER_CCW_CW(KC_LEFT, KC_RGHT)},
+    [LAYER_FUNCTION]   = {ENCODER_CCW_CW(KC_DOWN, KC_UP),    ENCODER_CCW_CW(KC_LEFT, KC_RGHT)},
+};
+// clang-format on
+#endif // ENCODER_MAP_ENABLE
diff --git a/users/flokli/keyboards/dilemma/rules.mk b/users/flokli/keyboards/dilemma/rules.mk
new file mode 100644
index 0000000000..5a090013dc
--- /dev/null
+++ b/users/flokli/keyboards/dilemma/rules.mk
@@ -0,0 +1,2 @@
+ENCODER_MAP_ENABLE = yes
+OPT_DEFS += -DMK_KINETIC_SPEED=1
diff --git a/users/flokli/keyboards/k6_pro/default.nix b/users/flokli/keyboards/k6_pro/default.nix
new file mode 100644
index 0000000000..49945b88ae
--- /dev/null
+++ b/users/flokli/keyboards/k6_pro/default.nix
@@ -0,0 +1,41 @@
+{ depot, pkgs, ... }:
+
+rec {
+  qmk_firmware_src = pkgs.fetchFromGitHub {
+    owner = "Keychron"; # the Keychron fork of qmk/qmk_firmware
+    repo = "qmk_firmware";
+    rev = "e0a48783e7cde92d1edfc53a8fff511c45e869d4"; # bluetooth_playground branch
+    hash = "sha256-Pk9kXktmej9JyvSt7UMEW2FDrBg7k1lOssh6HjrP5ro=";
+    fetchSubmodules = true;
+  };
+
+  firmware = pkgs.stdenv.mkDerivation {
+    name = "keychron-k6_pro-firmware";
+
+    src = qmk_firmware_src;
+
+    nativeBuildInputs = [
+      pkgs.qmk
+    ];
+
+    buildPhase = ''
+      mkdir -p keyboards/keychron/k6_pro/ansi/rgb/keymaps/flokli
+      cp ${./keymap.c} keyboards/keychron/k6_pro/ansi/rgb/keymaps/flokli/keymap.c
+      cp ${./rules.mk} keyboards/keychron/k6_pro/ansi/rgb/keymaps/flokli/rules.mk
+
+      make keychron/k6_pro/ansi/rgb:flokli
+    '';
+
+    installPhase = ''
+      mkdir -p $out
+
+      cp keychron_k6_pro_ansi_rgb_flokli.bin $out/
+    '';
+  };
+
+  flash = pkgs.writeShellScript "flash.sh" ''
+    QMK_HOME=${qmk_firmware_src} ${pkgs.qmk}/bin/qmk flash ${firmware}/keychron_k6_pro_ansi_rgb_flokli.bin
+  '';
+
+  meta.ci.targets = [ "firmware" ];
+}
diff --git a/users/flokli/keyboards/k6_pro/keymap.c b/users/flokli/keyboards/k6_pro/keymap.c
new file mode 100644
index 0000000000..1aa406eeac
--- /dev/null
+++ b/users/flokli/keyboards/k6_pro/keymap.c
@@ -0,0 +1,76 @@
+/* Copyright 2021 @ Keychron (https://www.keychron.com)
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include QMK_KEYBOARD_H
+
+// clang-format off
+enum layers{
+  MAC_BASE,
+  WIN_BASE,
+  MAC_FN1,
+  WIN_FN1,
+  FN2
+};
+
+const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {
+[MAC_BASE] = LAYOUT_ansi_68(
+     KC_ESC,   KC_1,     KC_2,     KC_3,     KC_4,     KC_5,     KC_6,     KC_7,     KC_8,     KC_9,     KC_0,     KC_MINS,  KC_EQL,   KC_BSPC, KC_DEL,
+     KC_TAB,   KC_Q,     KC_W,     KC_E,     KC_R,     KC_T,     KC_Y,     KC_U,     KC_I,     KC_O,     KC_P,     KC_LBRC,  KC_RBRC,  KC_BSLS, KC_HOME,
+     KC_CAPS,  KC_A,     KC_S,     KC_D,     KC_F,     KC_G,     KC_H,     KC_J,     KC_K,     KC_L,     KC_SCLN,  KC_QUOT,            KC_ENT,  KC_PGUP,
+     KC_LSFT,  KC_Z,     KC_X,     KC_C,     KC_V,     KC_B,     KC_N,     KC_M,     KC_COMM,  KC_DOT,   KC_SLSH,  KC_RSFT,  KC_UP,    KC_PGDN,
+     KC_LCTL,  KC_LOPTN, KC_LCMMD,                               KC_SPC,                       KC_RCMMD,MO(MAC_FN1),MO(FN2), KC_LEFT,  KC_DOWN, KC_RGHT),
+
+[WIN_BASE] = LAYOUT_ansi_68(
+     KC_ESC,   KC_1,     KC_2,     KC_3,     KC_4,     KC_5,     KC_6,     KC_7,     KC_8,     KC_9,     KC_0,     KC_MINS,  KC_EQL,   KC_BSPC, KC_DEL,
+     KC_TAB,   KC_Q,     KC_W,     KC_E,     KC_R,     KC_T,     KC_Y,     KC_U,     KC_I,     KC_O,     KC_P,     KC_LBRC,  KC_RBRC,  KC_BSLS, KC_HOME,
+     KC_CAPS,  KC_A,     KC_S,     KC_D,     KC_F,     KC_G,     KC_H,     KC_J,     KC_K,     KC_L,     KC_SCLN,  KC_QUOT,            KC_ENT,  KC_PGUP,
+     KC_LSFT,  KC_Z,     KC_X,     KC_C,     KC_V,     KC_B,     KC_N,     KC_M,     KC_COMM,  KC_DOT,   KC_SLSH,  KC_RSFT,            KC_UP,   KC_PGDN,
+     KC_LCTL,  KC_LGUI,  KC_LALT,                                KC_SPC,                       KC_RALT, MO(WIN_FN1),MO(FN2), KC_LEFT,  KC_DOWN, KC_RGHT),
+
+[MAC_FN1] = LAYOUT_ansi_68(
+     KC_GRV,   KC_BRID,  KC_BRIU,  KC_MCTL,  KC_LPAD,  RGB_VAD,  RGB_VAI,  KC_MPRV,  KC_MPLY,  KC_MNXT,  KC_MUTE,  KC_VOLD,  KC_VOLU,  _______,  RGB_TOG,
+     _______,  BT_HST1,  BT_HST2,  BT_HST3,  _______,  _______,  _______,  _______,  _______,  _______,  _______,  _______,  _______,  _______,  _______,
+     RGB_TOG,  RGB_MOD,  RGB_VAI,  RGB_HUI,  RGB_SAI,  RGB_SPI,  _______,  _______,  _______,  _______,  _______,  _______,            _______,  _______,
+     _______,  RGB_RMOD, RGB_VAD,  RGB_HUD,  RGB_SAD,  RGB_SPD,  NK_TOGG,  _______,  _______,  _______,  _______,  _______,  _______,  _______,
+     _______,  _______,  _______,                                _______,                      _______,  _______,  _______,  _______,  _______,  _______),
+
+[WIN_FN1] = LAYOUT_ansi_68(
+//                                           mic mute                      webcam    wifi
+     KC_GRV,   KC_MUTE,  KC_VOLD,  KC_VOLU,  _______,  KC_BRID,  KC_BRIU,  _______,  _______,  _______,  _______,  _______,  _______,  _______,  RGB_TOG,
+     _______,  BT_HST1,  BT_HST2,  BT_HST3,  _______,  _______,  _______,  _______,  _______,  _______,  _______,  _______,  _______,  _______,  _______,
+     RGB_TOG,  RGB_MOD,  RGB_VAI,  RGB_HUI,  RGB_SAI,  RGB_SPI,  _______,  _______,  _______,  _______,  _______,  _______,            _______,  _______,
+     _______,  RGB_RMOD, RGB_VAD,  RGB_HUD,  RGB_SAD,  RGB_SPD,  NK_TOGG,  _______,  _______,  _______,  _______,  _______,  _______,  _______,
+     _______,  _______,  _______,                                _______,                      KC_PSCR,  _______,  _______,  _______,  _______,  _______),
+
+[FN2] = LAYOUT_ansi_68(
+     KC_TILD,  KC_F1,    KC_F2,    KC_F3,    KC_F4,    KC_F5,    KC_F6,    KC_F7,    KC_F8,    KC_F9,    KC_F10,   KC_F11,   KC_F12,   _______,  KC_SLEP,
+     _______,  KC_BTN1,  KC_MS_U,  KC_BTN2,  KC_WH_U,  KC_VOLU,  KC_MUTE,  KC_MPLY,  _______,  _______,  _______,  _______,  _______,  _______,  _______,
+     _______,  KC_MS_L,  KC_MS_D,  KC_MS_R,  KC_WH_D,  KC_VOLD,  KC_MPRV,  KC_MNXT,  _______,  _______,  _______,  _______,            _______,  _______,
+     _______,  _______,  _______,  _______,  _______,  BAT_LVL,  _______,  _______,  _______,  _______,  _______,  _______,  _______,  _______,
+     _______,  _______,  _______,                                _______,                      _______,  _______,  _______,  _______,  _______,  _______),
+};
+
+// Shift+Del -> middle mouse button
+const key_override_t insert_key_override = ko_make_basic(MOD_MASK_SHIFT, KC_DEL, KC_BTN3);
+// Shift+Home -> End
+const key_override_t end_key_override = ko_make_basic(MOD_MASK_SHIFT, KC_HOME, KC_END);
+
+// This globally defines all key overrides to be used
+const key_override_t **key_overrides = (const key_override_t *[]) {
+     &insert_key_override,
+     &end_key_override,
+     NULL // end of array
+};
diff --git a/users/flokli/keyboards/k6_pro/rules.mk b/users/flokli/keyboards/k6_pro/rules.mk
new file mode 100644
index 0000000000..35725756d4
--- /dev/null
+++ b/users/flokli/keyboards/k6_pro/rules.mk
@@ -0,0 +1,2 @@
+KEY_OVERRIDE_ENABLE = yes
+OPT_DEFS += -DDYNAMIC_KEYMAP_LAYER_COUNT=5 -DMK_KINETIC_SPEED=1
diff --git a/users/flokli/nixos/.envrc b/users/flokli/nixos/.envrc
new file mode 100644
index 0000000000..ccf3cb847a
--- /dev/null
+++ b/users/flokli/nixos/.envrc
@@ -0,0 +1 @@
+PATH_add $(nix-build ../../.. -A users.flokli.nixos.deps --no-out-link)/bin
diff --git a/users/flokli/nixos/.skip-subtree b/users/flokli/nixos/.skip-subtree
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/users/flokli/nixos/.skip-subtree
diff --git a/users/flokli/nixos/archeology-ec2/OWNERS b/users/flokli/nixos/archeology-ec2/OWNERS
new file mode 100644
index 0000000000..b9bc074a80
--- /dev/null
+++ b/users/flokli/nixos/archeology-ec2/OWNERS
@@ -0,0 +1 @@
+edef
diff --git a/users/flokli/nixos/archeology-ec2/configuration.nix b/users/flokli/nixos/archeology-ec2/configuration.nix
new file mode 100644
index 0000000000..f0fc0c5d09
--- /dev/null
+++ b/users/flokli/nixos/archeology-ec2/configuration.nix
@@ -0,0 +1,35 @@
+{ depot, pkgs, modulesPath, ... }:
+
+{
+  imports = [
+    "${modulesPath}/virtualisation/amazon-image.nix"
+    ../profiles/archeology.nix
+  ];
+
+  systemd.timers.parse-bucket-logs = {
+    wantedBy = [ "multi-user.target" ];
+    timerConfig.OnCalendar = "*-*-* 03:00:00 UTC";
+  };
+
+  systemd.services.parse-bucket-logs = {
+    path = [ depot.users.flokli.archeology.parse-bucket-logs ];
+    serviceConfig = {
+      Type = "oneshot";
+      ExecStart = (pkgs.writers.writePython3 "parse-bucket-logs-continuously"
+        {
+          libraries = [ pkgs.python3Packages.boto3 ];
+        } ./parse-bucket-logs-continuously.py);
+      DynamicUser = "yes";
+      StateDirectory = "parse-bucket-logs";
+    };
+  };
+
+  environment.systemPackages = [
+    depot.users.flokli.archeology.parse-bucket-logs
+  ];
+
+  networking.hostName = "archeology-ec2";
+
+  system.stateVersion = "23.05"; # Did you read the comment?
+}
+
diff --git a/users/flokli/nixos/archeology-ec2/hardware-configuration.nix b/users/flokli/nixos/archeology-ec2/hardware-configuration.nix
new file mode 100644
index 0000000000..7b3d79d70a
--- /dev/null
+++ b/users/flokli/nixos/archeology-ec2/hardware-configuration.nix
@@ -0,0 +1,36 @@
+{ lib, modulesPath, ... }:
+
+{
+  imports =
+    [
+      (modulesPath + "/profiles/qemu-guest.nix")
+    ];
+
+  boot.initrd.availableKernelModules = [ "ahci" "xhci_pci" "virtio_pci" "sr_mod" "virtio_blk" ];
+  boot.initrd.kernelModules = [ ];
+  boot.kernelModules = [ "kvm-amd" ];
+  boot.extraModulePackages = [ ];
+
+  fileSystems."/" =
+    {
+      device = "/dev/disk/by-partlabel/root";
+      fsType = "xfs";
+    };
+
+  fileSystems."/boot" =
+    {
+      device = "/dev/disk/by-partlabel/boot";
+      fsType = "vfat";
+    };
+
+  swapDevices = [ ];
+
+  # Enables DHCP on each ethernet and wireless interface. In case of scripted networking
+  # (the default) this is the recommended approach. When using systemd-networkd it's
+  # still possible to use this option, but it's recommended to use it in conjunction
+  # with explicit per-interface declarations with `networking.interfaces.<interface>.useDHCP`.
+  networking.useDHCP = lib.mkDefault true;
+  # networking.interfaces.enp1s0.useDHCP = lib.mkDefault true;
+
+  nixpkgs.hostPlatform = lib.mkDefault "x86_64-linux";
+}
diff --git a/users/flokli/nixos/archeology-ec2/parse-bucket-logs-continuously.py b/users/flokli/nixos/archeology-ec2/parse-bucket-logs-continuously.py
new file mode 100644
index 0000000000..f6ec8fb77c
--- /dev/null
+++ b/users/flokli/nixos/archeology-ec2/parse-bucket-logs-continuously.py
@@ -0,0 +1,62 @@
+import boto3
+import datetime
+import os
+import re
+import subprocess
+import tempfile
+
+s3 = boto3.resource('s3')
+bucket_name = "nix-archeologist"
+prefix = "nix-cache-bucket-logs/"
+
+bucket = s3.Bucket(bucket_name)
+
+key_pattern = re.compile(r'.*\/(?P<y>\d{4})-(?P<m>\d{2})-(?P<d>\d{2})\.parquet$')  # noqa: E501
+
+# get a listing (which is sorted), grab the most recent key
+last_elem = list(
+    o for o in bucket.objects.filter(Prefix=prefix)
+    if key_pattern.match(o.key)
+).pop()
+
+# extract the date of that key.
+m = key_pattern.search(last_elem.key)
+last_elem_date = datetime.date(int(m.group("y")), int(m.group("m")), int(m.group("d")))  # noqa: E501
+
+# get the current date (UTC)
+now = datetime.datetime.now(tz=datetime.UTC)
+now_date = datetime.date(now.year, now.month, now.day)
+
+while True:
+    # Calculate what date would be processed next.
+    next_elem_date = last_elem_date + datetime.timedelta(days=1)
+
+    # If that's today, we don't want to process it.
+    if next_elem_date == now_date:
+        print("Caught up, would process data from today.")
+        break
+
+    # If we'd be processing data from yesterday, but it's right after midnight,
+    # also don't process - data might still be flushed.
+    if (next_elem_date + datetime.timedelta(days=1) == now_date) and now.hour == 0:  # noqa: E501
+        print("Not processing data from previous day right after midnight")
+        break
+
+    src = f"http://nix-cache-log.s3.amazonaws.com/log/{next_elem_date.isoformat()}-*"  # noqa: E501
+
+    # Invoke parse-bucket-logs script inside a tempdir and upload on success.
+    with tempfile.TemporaryDirectory() as td:
+        work_file_name = os.path.join(td, "output.parquet")
+        args = ["archeology-parse-bucket-logs", src, work_file_name]
+        subprocess.run(
+            args,
+            check=True  # throw exception if nonzero exit code
+        )
+
+        dest_key = f"{prefix}{next_elem_date.isoformat()}.parquet"
+
+        # Upload the file
+        print(f"uploading to s3://{bucket_name}{dest_key}")
+        bucket.upload_file(work_file_name, dest_key)
+
+    last_elem_date = next_elem_date
diff --git a/users/flokli/nixos/default.nix b/users/flokli/nixos/default.nix
new file mode 100644
index 0000000000..9ed223a908
--- /dev/null
+++ b/users/flokli/nixos/default.nix
@@ -0,0 +1,32 @@
+{ depot, pkgs, lib, ... }:
+
+let
+  systemFor = sys: (depot.ops.nixos.nixosFor sys).system;
+
+  # assumes `name` is configured appropriately in your .ssh/config
+  deployScript = name: sys: pkgs.writeShellScriptBin "deploy-${name}" ''
+    set -eo pipefail
+    nix-copy-closure --to ${name} --gzip --use-substitutes ${sys}
+    ssh ${name} nix-env --profile /nix/var/nix/profiles/system --set ${sys}
+    ssh ${name} ${sys}/bin/switch-to-configuration switch
+  '';
+
+in
+depot.nix.readTree.drvTargets rec {
+  archeologyEc2System = (depot.ops.nixos.nixosFor ({ ... }: {
+    imports = [
+      ./archeology-ec2/configuration.nix
+    ];
+  })).config.system.build.toplevel;
+
+  deploy-archeology-ec2 = (deployScript "archeology-ec2" archeologyEc2System);
+
+  deps = (depot.nix.lazy-deps {
+    deploy-archeology-ec2.attr = "users.flokli.nixos.deploy-archeology-ec2";
+  });
+
+  shell = pkgs.mkShell {
+    name = "flokli-nixos-shell";
+    packages = [ deps ];
+  };
+}
diff --git a/users/flokli/nixos/profiles/archeology.nix b/users/flokli/nixos/profiles/archeology.nix
new file mode 100644
index 0000000000..c87d6bcf30
--- /dev/null
+++ b/users/flokli/nixos/profiles/archeology.nix
@@ -0,0 +1,37 @@
+# Set of unconditional config options applicable to all archeology machines.
+
+{ depot, pkgs, ... }:
+
+{
+  # Use the TVL binary cache
+  tvl.cache.enable = true;
+
+  # Start clickhose as a system service.
+  services.clickhouse.enable = true;
+
+  # for ClickHouse
+  # We're keeping this here rather than in the NixOS module, because I suspect
+  # this opens up timing side channels. This is a single-user, single-purpose
+  # machine, so that isn't a concern here.
+  boot.kernel.sysctl."kernel.task_delayacct" = 1;
+
+  # Enable SSH and let edef and flokli in
+  services.openssh.enable = true;
+
+  users.users.root.openssh.authorizedKeys.keys = [
+    "cert-authority ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQCvb/7ojfcbKvHIyjnrNUOOgzy44tCkgXY9HLuyFta1jQOE9pFIK19B4dR9bOglPKf145CCL0mSFJNNqmNwwavU2uRn+TQrW+U1dQAk8Gt+gh3O49YE854hwwyMU+xD6bIuUdfxPr+r5al/Ov5Km28ZMlHOs3FoAP0hInK+eAibioxL5rVJOtgicrOVCkGoXEgnuG+LRbOYTwzdClhRUxiPjK8alCbcJQ53AeZHO4G6w9wTr+W5ILCfvW4OmUXCX01sKzaBiQuuFCF6M/H4LlnsPWLMra2twXxkOIhZblwC+lncps9lQaUgiD4koZeOCORvHW00G0L39ilFbbnVcL6Itp/m8RRWm/xRxS4RMnsdV/AhvpRLrhL3lfQ7E2oCeSM36v1S9rdg6a47zcnpL+ahG76Gz39Y7KmVRQciNx7ezbwxj3Q5lZtFykgdfGIAN+bT8ijXMO6m68g60i9Bz4IoMZGkiJGqMYLTxMQ+oRgR3Ro5lbj7E11YBHyeimoBYXYGHMkiuxopQZ7lIj3plxIzhmUlXJBA4jMw9KGHdYaLhaicIYhvQmCTAjrkt2HvxEe6lU8iws2Qv+pB6tAGundN36RVVWAckeQPZ4ZsgDP8V2FfibZ1nsrQ+zBKqaslYMAHs01Cf0Hm0PnCqagf230xaobu0iooNuXx44QKoDnB+w== edef"
+    "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPTVTXOutUZZjXLB0lUSgeKcSY/8mxKkC0ingGK1whD2 flokli"
+  ];
+
+  # Get a bunch of text editors and CLI tools.
+  environment.systemPackages = [
+    pkgs.awscli
+    pkgs.duckdb
+    pkgs.parquet-tools
+    pkgs.helix
+    pkgs.htop
+    pkgs.kakoune
+    pkgs.kitty.terminfo
+    pkgs.tmux
+  ];
+}
diff --git a/users/flokli/presentations/2023-09-09-nixcon-tvix/.gitignore b/users/flokli/presentations/2023-09-09-nixcon-tvix/.gitignore
new file mode 100644
index 0000000000..397b4a7624
--- /dev/null
+++ b/users/flokli/presentations/2023-09-09-nixcon-tvix/.gitignore
@@ -0,0 +1 @@
+*.log
diff --git a/users/flokli/presentations/2023-09-09-nixcon-tvix/architecture.dot b/users/flokli/presentations/2023-09-09-nixcon-tvix/architecture.dot
new file mode 100644
index 0000000000..a6ea0460ef
--- /dev/null
+++ b/users/flokli/presentations/2023-09-09-nixcon-tvix/architecture.dot
@@ -0,0 +1,5 @@
+digraph {
+    Builder
+    Store
+    Evaluator
+}
diff --git a/users/flokli/presentations/2023-09-09-nixcon-tvix/cppnix-example-lexer.cpp b/users/flokli/presentations/2023-09-09-nixcon-tvix/cppnix-example-lexer.cpp
new file mode 100644
index 0000000000..7c52bce8b6
--- /dev/null
+++ b/users/flokli/presentations/2023-09-09-nixcon-tvix/cppnix-example-lexer.cpp
@@ -0,0 +1,13 @@
+attrpath
+  : attrpath '.' attr {
+    $$ = $1; $1->push_back(AttrName(data->symbols.create($3)));
+  }
+  | attrpath '.' string_attr
+    { $$ = $1;
+      ExprString * str = dynamic_cast<ExprString *>($3);
+      if (str) {
+          $$->push_back(AttrName(data->symbols.create(str->s)));
+          delete str;
+      } else
+          $$->push_back(AttrName($3));
+    }
diff --git a/users/flokli/presentations/2023-09-09-nixcon-tvix/crate-deps.dot b/users/flokli/presentations/2023-09-09-nixcon-tvix/crate-deps.dot
new file mode 100644
index 0000000000..66ead74b1e
--- /dev/null
+++ b/users/flokli/presentations/2023-09-09-nixcon-tvix/crate-deps.dot
@@ -0,0 +1,19 @@
+digraph {
+    bgcolor="transparent"
+    node [fillcolor="lightgrey",style="filled"]
+
+    tvix_cli
+    tvix_eval
+    nix_compat
+    tvix_serde
+    tvix_store
+
+    tvix_cli -> tvix_store
+    tvix_cli -> nix_compat
+    tvix_cli -> tvix_eval
+
+    tvix_store -> nix_compat
+    tvix_eval -> nix_compat
+
+    tvix_serde -> tvix_eval
+}
diff --git a/users/flokli/presentations/2023-09-09-nixcon-tvix/default.nix b/users/flokli/presentations/2023-09-09-nixcon-tvix/default.nix
new file mode 100644
index 0000000000..1ec0a0bd0e
--- /dev/null
+++ b/users/flokli/presentations/2023-09-09-nixcon-tvix/default.nix
@@ -0,0 +1,37 @@
+{ depot, pkgs, ... }:
+
+let
+  inherit (pkgs)
+    fontconfig qrencode runCommand stdenv;
+  mkQr = url: runCommand "qrcode.png" { } ''
+    ${qrencode}/bin/qrencode -o $out -t SVG -s 5 \
+      --background=fafafa \
+      --foreground=000000 \
+      ${url}
+  '';
+in
+stdenv.mkDerivation {
+  name = "2023-nixcon-tvix";
+  src = ./.;
+
+  FONTCONFIG_FILE = pkgs.makeFontsConf {
+    fontDirectories = with pkgs; [ jetbrains-mono fira fira-code fira-mono lato ];
+  };
+
+  PUPPETEER_EXECUTABLE_PATH = "${pkgs.chromium}/bin/chromium";
+  PUPPETEER_SKIP_CHROMIUM_DOWNLOAD = "1";
+
+  nativeBuildInputs = [ pkgs.reveal-md pkgs.graphviz ];
+
+  buildPhase = ''
+    cp ${depot.tvix.logo}/logo.png tvix-logo.png
+    dot -Tsvg crate-deps.dot > crate-deps.svg
+    cp ${mkQr "https://flokli.de"} qrcode-flokli.svg
+    cp ${mkQr "https://tvix.dev"} qrcode-tvix.svg
+
+    mkdir -p $out
+    reveal-md --static $out presentation.md
+    reveal-md --print $out/slides.pdf presentation.md
+    cp tvixbolt.webm $out
+  '';
+}
diff --git a/users/flokli/presentations/2023-09-09-nixcon-tvix/presentation.md b/users/flokli/presentations/2023-09-09-nixcon-tvix/presentation.md
new file mode 100644
index 0000000000..b37554e188
--- /dev/null
+++ b/users/flokli/presentations/2023-09-09-nixcon-tvix/presentation.md
@@ -0,0 +1,294 @@
+---
+author:
+- Florian Klink
+date: 2023-09-09
+title: "Tvix: Status update"
+theme: moon
+revealOptions:
+  transition: 'fade'
+---
+
+# Tvix: Status update
+
+![Tvix Logo](tvix-logo.png)
+
+2023-09-09
+
+Florian Klink
+
+---
+
+## Whoami
+
+- flokli
+- nixpkgs contributor since 2018, maintaining systemd, nsncd and some
+  more stuff
+- Freelance Nix/DevOps consultant
+- I spend too much time on computers :-)
+
+---
+
+## What is Tvix?
+
+- A new implementation of Nix
+- modular
+- written in Rust
+- developed in the [TVL](https://tvl.fyi) monorepo
+- subtree exported to [github:tvlfyi/tvix](https://github.com/tvlfyi/tvix)
+
+---
+
+## Structure
+
+- strong separation between **Evaluator**, **Store** and **Builder**
+- Defined interfaces between different components (Protobuf/gRPC) <!-- .element: class="fragment" -->
+  - Allows adding to/combining with your own components <!-- .element: class="fragment" -->
+- <!-- .element: class="fragment" --> A lot of helper code for some of the Nix internals in the `nix-compat` crate
+
+Note: Derivation types, serializers. NAR writers, nixbase32 enc/dec, Nix Hash function, stringparsing etc.
+
+----
+
+![crate-deps.svg](crate-deps.svg)
+
+---
+
+## Evaluator: Design
+
+- <!-- .element: class="fragment" --> 
+  Nix code is parsed via [rnix](https://github.com/nix-community/rnix-parser)
+- <!-- .element: class="fragment" -->
+  AST traversal, generate bytecode (with some transformations)
+- <!-- .element: class="fragment" -->
+  Bytecode is executed by an abstract machine with a Nix-specific instruction set
+
+----
+
+## Evaluator: Design
+
+- <!-- .element: class="fragment" -->
+  Builtins are separate from the "evaluator core"
+  - <!-- .element: class="fragment" -->
+    inject your own builtins
+  - <!-- .element: class="fragment" -->
+    this includes `builtins.derivation`!
+- <!-- .element: class="fragment" -->
+  IO is nicely abstracted away
+  - <!-- .element: class="fragment" -->
+    We can run a Nixlang subset without IO in wasm (see [tvixbolt](https://bolt.tvix.dev/)),
+    or parse Nix into a config struct with `tvix-serde`
+
+----
+
+<!-- <video class="r-stretch" src="./tvixbolt.webm"></video> -->
+<a href="./tvixbolt.webm">Tvixbolt Demo</a>
+
+----
+
+### Evaluator: Current Work
+
+- <!-- .element: class="fragment" -->
+  Current goal: **evaluate nixpkgs the same way as Nix does**
+- <!-- .element: class="fragment" -->
+  Checked by comparing the calculated output paths, which checks correctness of all \"parent\" output paths too.
+- <!-- .element: class="fragment" -->
+  Required implementing a lot of Nix internals in `nix-compat`, and `tvix-store` (A-Term, hash modulo, NAR writer/hasher)
+
+Note: Getting output hashing correct, and exposing this in a re-usable fashion took quite some iterations to get right.
+
+----
+
+### Evaluator: Current Work (cont.)
+- <!-- .element: class="fragment" -->
+  ๐ŸŽ‰ Already correct for (and continously checked by CI on every commit):
+  - <!-- .element: class="fragment" -->
+  `stdenv`, `hello`
+  - <!-- .element: class="fragment" -->
+  `pkgsCross.aarch64-multiplatform.stdenv`, `pkgsCross.aarch64-multiplatform.hello`
+- <!-- .element: class="fragment" -->
+  Some work left for more complicated expressions
+  - <!-- .element: class="fragment" -->
+    infinite recursion [when inheriting from a `builtins.tryEval` multiple times](https://b.tvl.fyi/281)
+  - <!-- .element: class="fragment" -->
+    small details around file imports
+- <!-- .element: class="fragment" -->
+  Not too much performance finetuning until we're correct first.
+
+----
+
+### Evaluator: Demo
+
+[![asciicast](https://asciinema.org/a/MH4tuVPLsKewJSGJUYEyIKUpj.svg)](https://asciinema.org/a/MH4tuVPLsKewJSGJUYEyIKUpj)
+
+---
+
+## Store: Design
+
+- <!-- .element: class="fragment" -->
+  Uses a very different underlying data model:
+  - <!-- .element: class="fragment" -->
+    Nix stores on a per- `StorePath` granularity
+  - <!-- .element: class="fragment" -->
+    tvix-store uses a Merkle DAG of directories, similar to git trees, but with [BLAKE3](https://github.com/BLAKE3-team/BLAKE3) digests as pointers.
+  - <!-- .element: class="fragment" -->
+    Compat layer in front to still render/calculate NAR on demand where needed
+  - <!-- .element: class="fragment" -->
+    Substitution, caching, ... possible to describe via composition/layering
+
+----
+
+![tvix-store graph](tvix-store-graph.svg)
+
+----
+
+### Store: Advantages
+
+- <!-- .element: class="fragment" -->
+  Less downloading of data that didn't change
+- <!-- .element: class="fragment" -->
+  Less duplicate data on disk/storage
+- <!-- .element: class="fragment" -->
+  Inherently content-addressed, so P2P substitution possible
+- <!-- .element: class="fragment" -->
+  Allows doing verified blob streaming ([BAO](https://github.com/oconnor663/bao), [bao-tree](https://github.com/n0-computer/bao-tree))
+- <!-- .element: class="fragment" -->
+  Protocol has some \"smarter\" methods to avoid roundtrips, but could all be statically pre-rendered
+- <!-- .element: class="fragment" -->
+  Very little data that needs to be fetched from a trusted party (or be signed)
+
+Note: Our way of addressing blobs by their raw blake3 digest is natively compatible with iroh, the IPFS Re-implementation in Rust
+
+----
+
+### Store: Status
+
+- <!-- .element: class="fragment" -->
+  Whole Merkle-based store implementation (and Nix NAR compat layer) is there
+  - <!-- .element: class="fragment" -->
+    exercised by the output path CI tests, and a test suite.
+  - <!-- .element: class="fragment" -->
+    three backends (Sled, in-memory, gRPC client)
+  - <!-- .element: class="fragment" -->
+    more backends and more test suites planned.
+- <!-- .element: class="fragment" -->
+  FUSE filesystem to expose the store (to Tvix Builders, \"appliances\") <!-- .element: class="fragment" -->
+
+Note: backends: RocksDB, sqlite, s3. fuse: lazy fetching of build input files | think about a minimal initrd to bring up network and mount the store, then run any closure.
+
+----
+
+### Store: Demo
+
+[![asciicast](https://asciinema.org/a/YFB9yycHdx0OUH9N0WdAkIYua.svg)](https://asciinema.org/a/YFB9yycHdx0OUH9N0WdAkIYua)
+
+----
+
+### Store: Status (cont.)
+- <!-- .element: class="fragment" -->
+  More work on store composition needed (necessary for substition and caching)
+- <!-- .element: class="fragment" -->
+  More work on more granular blob substititon needed.
+- <!-- .element: class="fragment" -->
+  More work on bridges with Nix needed
+  - <!-- .element: class="fragment" -->
+    Get Nix to talk to a tvix-store
+  - <!-- .element: class="fragment" -->
+    Expose existing binary caches to tvix-store
+
+---
+
+### Builder: Design
+
+- <!-- .element: class="fragment" -->
+  Build requests/Build protocol is less Nix-specific
+  - <!-- .element: class="fragment" -->
+    allows reusing builders for other usages (non-sandboxed builds, other build systems, playing with other addressing mechanisms)
+- <!-- .element: class="fragment" -->
+  Distinction between a **specific build attempt** and the **general build recipe**
+  - <!-- .element: class="fragment" -->
+    allows keeping metadata about failed builds
+  - <!-- .element: class="fragment" -->
+    stats (memory/cpu consumption)
+  - <!-- .element: class="fragment" -->
+    comparing different produced binary outputs (r11y)
+
+----
+
+### Builder: Design
+
+- <!-- .element: class="fragment" -->
+  Invididual builds can be run in your desired container/virt engine/scheduler, as long as it speaks the same Build API
+- <!-- .element: class="fragment" -->
+  Build API composition/proxying, similar to Store composition
+- <!-- .element: class="fragment" -->
+  allows "unattended building" (evaluate nixpkgs locally and send all build requests to a remote builder)
+- <!-- .element: class="fragment" -->
+  allows tailing logs from currently/already running builds
+
+----
+
+### Builder: Status
+
+- <!-- .element: class="fragment" -->
+  Dummy Builder implementation in `go-nix` (using OCI)
+- <!-- .element: class="fragment" -->
+  Some scribble notes on the Build Protocol
+- <!-- .element: class="fragment" -->
+  Glue code to trigger builds from inside `builtins.derivation` needs to be written
+- <!-- .element: class="fragment" -->
+  Builder implementation (using `systemd-nspwan` or some container engine needs to be written.
+- <!-- .element: class="fragment" -->
+  Web interface to visualize store contents and build graphs/builds/logs
+
+---
+
+## Contributing
+
+- <!-- .element: class="fragment" -->
+  Join the IRC channel (`#tvl` on `hackint`), bridged to Matrix and XMPP
+- <!-- .element: class="fragment" -->
+  Check our issue tracker
+- <!-- .element: class="fragment" -->
+  Try to use it and tell us how you broke it!
+- <!-- .element: class="fragment" -->
+  Add various Nix bits to `nix-compat`
+
+Note: or if you like what you seeing and want to sponsor parts, that's also cool :-)
+
+---
+
+# Thanks to...
+
+- <!-- .element: class="fragment" -->
+  all TVL contributors (some drive-by, some long-term contributors)
+- <!-- .element: class="fragment" -->
+  countless Nix community members for their input on the architecture and rubberducking
+- <!-- .element: class="fragment" -->
+  NLNET and others to sponsor parts of this
+
+----
+
+# Questions?
+
+<style>
+.container{
+    display: flex;
+}
+.col{
+    flex: 1;
+}
+</style>
+
+<div class="container">
+
+<div class="col">
+Florian Klink / <a href="https://flokli.de">flokli.de</a><br />
+<img src="qrcode-flokli.svg" />
+</div>
+
+<div class="col">
+Tvix / <a href="https://tvix.dev">tvix.dev</a><br />
+<img src="qrcode-tvix.svg" />
+</div>
+
+</div>
diff --git a/users/flokli/presentations/2023-09-09-nixcon-tvix/tvix-store-graph.svg b/users/flokli/presentations/2023-09-09-nixcon-tvix/tvix-store-graph.svg
new file mode 100644
index 0000000000..56338b587e
--- /dev/null
+++ b/users/flokli/presentations/2023-09-09-nixcon-tvix/tvix-store-graph.svg
@@ -0,0 +1,17 @@
+<svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1297.1484375 836.19140625" width="1297.1484375" height="836.19140625">
+  <!-- svg-source:excalidraw -->
+  
+  <defs>
+    <style class="style-fonts">
+      @font-face {
+        font-family: "Virgil";
+        src: url("https://excalidraw.com/Virgil.woff2");
+      }
+      @font-face {
+        font-family: "Cascadia";
+        src: url("https://excalidraw.com/Cascadia.woff2");
+      }
+    </style>
+    
+  </defs>
+  <rect x="0" y="0" width="1297.1484375" height="836.19140625" fill="#ffffff"></rect><g stroke-linecap="round" transform="translate(10 64.05078125) rotate(0 71.10546875 23.685546875)"><path d="M3.15 2.74 C3.15 2.74, 3.15 2.74, 3.15 2.74 M3.15 2.74 C3.15 2.74, 3.15 2.74, 3.15 2.74 M1.58 10.65 C3.55 6.56, 7.08 5.83, 10.11 0.84 M1.58 10.65 C4.54 7.52, 5.83 5.33, 10.11 0.84 M1.97 16.29 C5.97 12.52, 7.97 9.89, 14.44 1.95 M1.97 16.29 C5.39 12.84, 8.96 8.01, 14.44 1.95 M1.71 22.69 C5.44 18.07, 11.5 12.49, 19.43 2.31 M1.71 22.69 C6.47 16.78, 11.69 12.31, 19.43 2.31 M1.45 29.09 C9.97 18.06, 21.19 8.77, 25.07 1.92 M1.45 29.09 C9.61 17.76, 19.35 7.56, 25.07 1.92 M1.85 34.73 C6.66 29.67, 14.83 20.48, 30.06 2.28 M1.85 34.73 C8.29 27.25, 17.05 18.03, 30.06 2.28 M2.24 40.37 C10.77 28.82, 20.92 18.16, 35.7 1.88 M2.24 40.37 C14.65 26.46, 25.75 13.13, 35.7 1.88 M3.29 45.26 C19.71 28.3, 32.79 9.79, 40.69 2.24 M3.29 45.26 C14.19 32.96, 23.82 22.41, 40.69 2.24 M6.97 47.13 C21.76 30.34, 38.17 12.66, 45.67 2.6 M6.97 47.13 C19.37 32.8, 32.58 18.66, 45.67 2.6 M12.61 46.74 C23.7 34.53, 32.71 23.85, 51.32 2.21 M12.61 46.74 C23.04 33.52, 34.64 21.81, 51.32 2.21 M17.6 47.1 C32.92 32.2, 44.82 17.61, 56.3 2.57 M17.6 47.1 C26.86 36.54, 34.43 27.02, 56.3 2.57 M22.58 47.46 C36.55 31.33, 51.38 11.55, 61.95 2.17 M22.58 47.46 C35.33 33.95, 46.66 20.15, 61.95 2.17 M28.23 47.06 C40.8 33.63, 55.34 16.98, 66.93 2.53 M28.23 47.06 C36.54 38.37, 43.25 29.02, 66.93 2.53 M33.21 47.42 C48.21 29.57, 61.74 14.8, 72.58 2.14 M33.21 47.42 C44.03 35.16, 55.8 21.76, 72.58 2.14 M38.86 47.03 C49.09 34.45, 58.15 26.51, 77.56 2.5 M38.86 47.03 C53.73 30.24, 67.18 15.29, 77.56 2.5 M43.84 47.39 C57.75 33, 73.31 14.12, 82.55 2.86 M43.84 47.39 C53.3 37.24, 61.17 27.55, 82.55 2.86 M49.49 46.99 C62.68 35.47, 70.45 21.6, 88.19 2.46 M49.49 46.99 C61.84 34.86, 73.11 21.46, 88.19 2.46 M54.47 47.35 C61.1 38.29, 69.25 27.83, 93.18 2.82 M54.47 47.35 C66.26 32.61, 76.99 20.02, 93.18 2.82 M60.12 46.96 C73.27 30.81, 84.64 16.32, 98.82 2.43 M60.12 46.96 C69.94 34.83, 80.66 21.92, 98.82 2.43 M65.1 47.32 C73.48 35.83, 86.65 26.47, 103.81 2.79 M65.1 47.32 C78.64 32.14, 91.97 18.02, 103.81 2.79 M70.09 47.68 C85.6 30.67, 98.3 12.05, 109.45 2.4 M70.09 47.68 C81.34 34.02, 94.32 21.42, 109.45 2.4 M75.73 47.28 C89.43 31.61, 102.84 18.22, 114.44 2.76 M75.73 47.28 C85.67 35.55, 96.35 22.65, 114.44 2.76 M80.72 47.64 C91.93 34.98, 103.63 17.52, 119.43 3.12 M80.72 47.64 C90.36 36.8, 98.88 24.6, 119.43 3.12 M86.36 47.25 C101.83 29.55, 117.32 12.7, 125.07 2.72 M86.36 47.25 C99.66 32.13, 111.08 17.18, 125.07 2.72 M91.35 47.61 C104.35 33.22, 117.8 19.95, 130.06 3.08 M91.35 47.61 C103.56 34.45, 114.2 20.27, 130.06 3.08 M96.99 47.21 C103.59 38.71, 113.94 28.96, 136.36 1.93 M96.99 47.21 C106.26 36.64, 117.24 24.44, 136.36 1.93 M101.98 47.57 C111.9 34.49, 126.09 20.88, 140.03 3.8 M101.98 47.57 C114.1 33.1, 128.09 16.65, 140.03 3.8 M106.97 47.93 C114.85 39.44, 121.62 28.8, 141.74 7.93 M106.97 47.93 C117.52 37.38, 125.94 26.56, 141.74 7.93 M112.61 47.54 C120.79 38.54, 126.18 28.87, 144.1 11.31 M112.61 47.54 C125.62 33.32, 137.05 19.84, 144.1 11.31 M117.6 47.9 C122.32 42.89, 130.44 32.85, 144.5 16.96 M117.6 47.9 C126.99 36.5, 135.5 27.33, 144.5 16.96 M123.24 47.51 C127.95 43.64, 132.17 36.33, 144.23 23.35 M123.24 47.51 C129.47 41.35, 136.09 32.69, 144.23 23.35 M128.23 47.87 C131.28 40.54, 137.28 36.59, 143.97 29.75 M128.23 47.87 C132.3 43.29, 136.94 37.23, 143.97 29.75 M132.56 48.98 C136.69 46.03, 136.69 42.77, 143.71 36.15 M132.56 48.98 C136.02 46.4, 138.73 42.29, 143.71 36.15" stroke="#a5d8ff" stroke-width="0.5" fill="none"></path><path d="M11.84 0 M11.84 0 C38.02 2.65, 61.84 0.28, 130.37 0 M11.84 0 C38.21 1.94, 65.76 1.5, 130.37 0 M130.37 0 C139.47 1, 143.75 5.88, 142.21 11.84 M130.37 0 C137.21 0.92, 139.99 4.87, 142.21 11.84 M142.21 11.84 C140.42 17.68, 141.72 25.2, 142.21 35.53 M142.21 11.84 C142.2 20.55, 141.47 28.9, 142.21 35.53 M142.21 35.53 C143.14 42.65, 138.92 45.76, 130.37 47.37 M142.21 35.53 C141.26 45.41, 136.07 48.58, 130.37 47.37 M130.37 47.37 C100.03 46.91, 70.33 46.17, 11.84 47.37 M130.37 47.37 C93.27 45.68, 57.65 45.47, 11.84 47.37 M11.84 47.37 C3.83 47.21, 0.04 43.58, 0 35.53 M11.84 47.37 C3.58 49.37, 2.09 44.62, 0 35.53 M0 35.53 C-0.11 25.56, 1.74 17.16, 0 11.84 M0 35.53 C-0.73 27.14, 0.25 16.41, 0 11.84 M0 11.84 C1.26 4.93, 5.18 0.97, 11.84 0 M0 11.84 C0.62 1.9, 3.42 2.05, 11.84 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(15 78.13632812499998) rotate(0 65.625 9.599999999999994)"><text x="0" y="0" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">0x01 0x02 0x03</text></g><g stroke-linecap="round" transform="translate(11.80078125 149.404296875) rotate(0 51.5 24.5)"><path d="M3.26 2.83 C3.26 2.83, 3.26 2.83, 3.26 2.83 M3.26 2.83 C3.26 2.83, 3.26 2.83, 3.26 2.83 M1.69 10.74 C4.79 9.22, 6.14 6.4, 10.22 0.93 M1.69 10.74 C4.82 7.44, 8.36 3.46, 10.22 0.93 M1.43 17.14 C5.08 10.94, 12.94 4.63, 14.55 2.05 M1.43 17.14 C7.42 12.34, 11.48 5.83, 14.55 2.05 M1.82 22.78 C7.79 17.76, 9.92 11.78, 20.19 1.65 M1.82 22.78 C6.91 17.54, 10.69 12.89, 20.19 1.65 M1.56 29.18 C10.68 19.58, 16.05 8.86, 25.18 2.01 M1.56 29.18 C6.66 24.1, 10.7 18.88, 25.18 2.01 M1.95 34.82 C14.06 20.7, 22.56 11.87, 30.16 2.37 M1.95 34.82 C12.05 23.95, 21.94 11.12, 30.16 2.37 M1.69 41.22 C7.98 31.97, 17.66 24.23, 35.81 1.98 M1.69 41.22 C14.67 26.87, 25.96 11.6, 35.81 1.98 M3.4 45.35 C12.42 31.5, 21.88 20.01, 40.79 2.34 M3.4 45.35 C14.79 32.81, 25.98 18.39, 40.79 2.34 M5.76 48.73 C19.03 33.54, 30.13 20.75, 46.44 1.94 M5.76 48.73 C15.95 35.19, 26.77 22.13, 46.44 1.94 M10.75 49.09 C25.08 35.85, 34.98 19.45, 51.42 2.3 M10.75 49.09 C25.36 34.29, 38.07 17.14, 51.42 2.3 M14.42 50.96 C25.24 34.64, 40.8 20.23, 57.07 1.91 M14.42 50.96 C31.56 31.52, 47.54 13.07, 57.07 1.91 M20.07 50.57 C36.58 31.78, 51.41 11.57, 62.05 2.27 M20.07 50.57 C35.27 32.37, 49.62 15.57, 62.05 2.27 M25.05 50.93 C33.69 40.39, 45.49 30.76, 67.7 1.87 M25.05 50.93 C39.57 35.58, 52.89 19.97, 67.7 1.87 M30.04 51.29 C42.51 36.46, 56.98 20.61, 72.68 2.23 M30.04 51.29 C39.08 40.34, 49.2 29.9, 72.68 2.23 M35.68 50.89 C47.44 39.9, 56.68 27.08, 78.33 1.84 M35.68 50.89 C52.05 33.69, 66.26 15.86, 78.33 1.84 M40.67 51.25 C53.32 36.02, 69.16 19.85, 83.31 2.2 M40.67 51.25 C53.19 37.93, 64.14 24.78, 83.31 2.2 M46.31 50.86 C62.11 32.31, 78.52 15.58, 88.96 1.8 M46.31 50.86 C57.68 37.06, 69.81 23.8, 88.96 1.8 M51.3 51.22 C59.29 38.98, 72.37 29.04, 93.94 2.16 M51.3 51.22 C64.73 35.51, 79.44 17.72, 93.94 2.16 M56.94 50.83 C69.81 34.76, 86.63 19.59, 98.93 2.52 M56.94 50.83 C72.69 32.66, 90.36 14.37, 98.93 2.52 M61.93 51.19 C77.07 35.49, 89.58 18.3, 101.95 5.15 M61.93 51.19 C76.05 33.49, 92.37 16.74, 101.95 5.15 M67.57 50.79 C76.42 41.59, 82.64 33.63, 103 10.04 M67.57 50.79 C76.25 40.25, 84.99 30.22, 103 10.04 M72.56 51.15 C79.09 44.86, 86.76 36.01, 103.4 15.68 M72.56 51.15 C78.4 43.41, 84.37 36.12, 103.4 15.68 M78.2 50.76 C84.15 41.95, 92.56 33.07, 103.13 22.08 M78.2 50.76 C88.29 39.34, 96.97 28.67, 103.13 22.08 M83.19 51.12 C88.53 45.31, 96.1 36.68, 103.53 27.72 M83.19 51.12 C91.24 41.6, 98.32 33.94, 103.53 27.72 M88.83 50.72 C90.5 47.29, 97.87 43.07, 103.27 34.12 M88.83 50.72 C94.68 44.06, 98.18 39.73, 103.27 34.12 M93.82 51.08 C96.56 47.12, 100.38 44.41, 104.97 38.25 M93.82 51.08 C96.61 47.17, 101.29 42.48, 104.97 38.25" stroke="#a5d8ff" stroke-width="0.5" fill="none"></path><path d="M12.25 0 M12.25 0 C28.51 0, 42.5 2.25, 90.75 0 M12.25 0 C32.14 -0.62, 52.39 1.04, 90.75 0 M90.75 0 C100.71 0.09, 101.8 3.77, 103 12.25 M90.75 0 C97.64 -0.83, 104 5.94, 103 12.25 M103 12.25 C101.33 19.82, 101.63 32.15, 103 36.75 M103 12.25 C102.35 21.13, 102.15 31.06, 103 36.75 M103 36.75 C104.56 46.87, 99.61 50.21, 90.75 49 M103 36.75 C104.15 46.69, 101.13 47.94, 90.75 49 M90.75 49 C62.94 48.31, 35.39 47.65, 12.25 49 M90.75 49 C67.73 49.76, 43.51 49.69, 12.25 49 M12.25 49 C3.6 47.48, -1.71 45.84, 0 36.75 M12.25 49 C3.19 49.75, -1.85 43.96, 0 36.75 M0 36.75 C-0.6 25.33, 0.5 18.11, 0 12.25 M0 36.75 C-0.07 30.3, -1.01 22.9, 0 12.25 M0 12.25 C-1.34 4.43, 2.32 -0.12, 12.25 0 M0 12.25 C-0.18 4.12, 4.26 -0.37, 12.25 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(16.80078125 164.30429687499998) rotate(0 42.1875 9.599999999999994)"><text x="0" y="0" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">0x04 0x05</text></g><g stroke-linecap="round" transform="translate(14.93359375 236.67578125) rotate(0 4.138671875 25.552734375)"><path d="M-0.29 2.4 C-0.29 2.4, -0.29 2.4, -0.29 2.4 M-0.29 2.4 C-0.29 2.4, -0.29 2.4, -0.29 2.4 M0.11 8.04 C1.82 6.13, 2.96 4.61, 4.7 2.76 M0.11 8.04 C1.05 6.92, 2.71 5.33, 4.7 2.76 M-0.15 14.44 C2.61 10.36, 4.52 9.27, 9.69 3.12 M-0.15 14.44 C3 10.45, 7.79 5.81, 9.69 3.12 M0.24 20.08 C1.78 17.33, 6.21 13.86, 9.43 9.52 M0.24 20.08 C3.9 16.52, 6.5 12.39, 9.43 9.52 M-0.02 26.48 C2.46 23.23, 7.19 20.21, 9.82 15.16 M-0.02 26.48 C2.52 23.58, 5.92 19.96, 9.82 15.16 M-0.28 32.88 C3.43 29.78, 4.96 26.51, 9.56 21.56 M-0.28 32.88 C1.68 30.37, 4.75 26.47, 9.56 21.56 M0.11 38.52 C2.43 35.55, 6.84 32.06, 9.3 27.96 M0.11 38.52 C3.34 34.69, 7.79 30.27, 9.3 27.96 M-0.15 44.92 C4.41 39.72, 7.29 36.44, 9.69 33.6 M-0.15 44.92 C2.92 41.4, 5.54 37.99, 9.69 33.6 M0.9 49.81 C2.58 47.91, 5.61 43.76, 9.43 40 M0.9 49.81 C3.14 46.67, 5.91 43.78, 9.43 40 M3.92 52.43 C5.01 50.49, 6.18 49.02, 9.82 45.64 M3.92 52.43 C5.89 50.77, 7.52 48.28, 9.82 45.64" stroke="#a5d8ff" stroke-width="0.5" fill="none"></path><path d="M2.07 0 M2.07 0 C2.97 -0.21, 4.11 0.12, 6.21 0 M2.07 0 C3.19 -0.16, 4.23 0.11, 6.21 0 M6.21 0 C8.97 0.45, 9.01 1.39, 8.28 2.07 M6.21 0 C9.57 -1.98, 9.89 2.6, 8.28 2.07 M8.28 2.07 C8.66 12.58, 9.73 22.31, 8.28 49.04 M8.28 2.07 C8.53 15.26, 7.49 28.86, 8.28 49.04 M8.28 49.04 C6.95 49.38, 7.58 52.75, 6.21 51.11 M8.28 49.04 C10.35 52.7, 9.64 53.24, 6.21 51.11 M6.21 51.11 C5.12 51.08, 3.76 50.9, 2.07 51.11 M6.21 51.11 C5.14 51.07, 4.25 51.01, 2.07 51.11 M2.07 51.11 C2.45 52.5, -1.17 52.21, 0 49.04 M2.07 51.11 C-1.47 50.3, 2.23 51.76, 0 49.04 M0 49.04 C-0.13 38.39, -0.48 26.48, 0 2.07 M0 49.04 C-1.31 32.15, -0.35 13.45, 0 2.07 M0 2.07 C0.41 0.36, -0.67 -0.56, 2.07 0 M0 2.07 C-0.35 -0.58, -1.03 -1.54, 2.07 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(10.23046875 10) rotate(0 23.4375 9.599999999999994)"><text x="0" y="0" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#1971c2" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">Blobs</text></g><g transform="translate(279.12890625 12.759374999999977) rotate(0 51.5625 9.599999999999994)"><text x="0" y="0" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#f08c00" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">Directories</text></g><g stroke-linecap="round" transform="translate(283.875 68.3828125) rotate(0 197 72.5)"><path d="M7.86 8.16 C7.86 8.16, 7.86 8.16, 7.86 8.16 M7.86 8.16 C7.86 8.16, 7.86 8.16, 7.86 8.16 M3.66 19.09 C8.28 14.49, 8.49 11.04, 18.1 2.48 M3.66 19.09 C7.7 14.57, 11.52 11.66, 18.1 2.48 M2.75 26.24 C11.51 19.05, 17.92 11.98, 24.4 1.33 M2.75 26.24 C7.23 20.55, 12.55 15.89, 24.4 1.33 M2.48 32.64 C12.12 24.05, 19.28 13.72, 30.7 0.18 M2.48 32.64 C9.16 24.56, 14.83 17.39, 30.7 0.18 M2.88 38.28 C11.43 28.28, 22.31 16.44, 34.37 2.05 M2.88 38.28 C14.6 24.25, 27.89 9.94, 34.37 2.05 M2.62 44.68 C9.32 34.89, 17.39 26.88, 39.36 2.41 M2.62 44.68 C11.95 33.82, 21.66 21.02, 39.36 2.41 M2.36 51.07 C12.81 41.21, 19.43 27.76, 45 2.02 M2.36 51.07 C16.68 33.29, 30.46 16.46, 45 2.02 M2.75 56.72 C16.64 41.98, 28.98 26.21, 49.99 2.38 M2.75 56.72 C21 35.95, 39.52 14.81, 49.99 2.38 M2.49 63.11 C19.63 44.26, 39.08 21.21, 55.63 1.98 M2.49 63.11 C17.01 46.73, 31.61 30.35, 55.63 1.98 M2.88 68.76 C22.9 46.33, 43.08 21.26, 60.62 2.34 M2.88 68.76 C23.19 45.39, 41.88 24.57, 60.62 2.34 M2.62 75.16 C23.83 54.32, 39.89 29.17, 66.26 1.95 M2.62 75.16 C18.47 56.13, 34.46 38.2, 66.26 1.95 M2.36 81.55 C29.87 50.88, 54.51 22.49, 71.25 2.31 M2.36 81.55 C26.5 54.38, 49.03 26.57, 71.25 2.31 M2.76 87.2 C25.76 61.24, 52.69 29.56, 76.89 1.91 M2.76 87.2 C22.68 64.01, 42.85 40.22, 76.89 1.91 M2.49 93.59 C30.49 61.54, 57.87 26.06, 81.88 2.27 M2.49 93.59 C31.33 60.52, 59.32 27.03, 81.88 2.27 M2.89 99.24 C28.16 70.88, 49.29 44.38, 87.52 1.88 M2.89 99.24 C22.19 77.54, 40.48 55.68, 87.52 1.88 M2.63 105.64 C27.42 78.52, 50.89 50.01, 92.51 2.24 M2.63 105.64 C38.6 64.43, 74.48 23.22, 92.51 2.24 M2.37 112.03 C35.69 71.87, 67.49 34.66, 98.15 1.85 M2.37 112.03 C29.87 80.37, 57.31 48.8, 98.15 1.85 M2.76 117.68 C35.35 78.95, 70.62 41.89, 103.14 2.21 M2.76 117.68 C24.16 94.43, 44.43 69.26, 103.14 2.21 M3.16 123.32 C27.31 95.67, 53.64 64.97, 108.78 1.81 M3.16 123.32 C40.3 79.27, 78.36 37.25, 108.78 1.81 M4.86 127.45 C35.22 90.31, 66.42 52.47, 113.77 2.17 M4.86 127.45 C27.89 100.07, 53.15 74.47, 113.77 2.17 M5.26 133.1 C41.54 89.35, 82.58 48.1, 119.41 1.78 M5.26 133.1 C50.78 80.9, 96.57 28.36, 119.41 1.78 M7.62 136.47 C50.54 85.68, 96.45 32.28, 124.4 2.14 M7.62 136.47 C50.54 87.26, 95.13 37.29, 124.4 2.14 M10.64 139.1 C44.47 96.28, 83.89 53.02, 130.04 1.74 M10.64 139.1 C49.89 96.71, 86.98 53.36, 130.04 1.74 M14.31 140.97 C55.74 92.08, 97.64 42.48, 135.03 2.1 M14.31 140.97 C49.36 97.94, 86.03 55.71, 135.03 2.1 M17.33 143.59 C62.88 90.12, 107.49 38.88, 140.67 1.71 M17.33 143.59 C52.29 102.09, 87.68 60.93, 140.67 1.71 M21.01 145.46 C48.28 114.13, 76.95 81.48, 145.66 2.07 M21.01 145.46 C64.61 95.84, 107.71 47.87, 145.66 2.07 M25.34 146.58 C73.23 89.86, 125.31 35.24, 150.65 2.43 M25.34 146.58 C51.9 117.94, 78.32 86.29, 150.65 2.43 M32.29 144.67 C78.07 95.21, 118.24 44.56, 156.29 2.03 M32.29 144.67 C74.54 98.13, 114.59 49.94, 156.29 2.03 M37.28 145.03 C85.09 88.58, 133.69 36.74, 161.28 2.39 M37.28 145.03 C69.44 108.12, 100.26 71.2, 161.28 2.39 M42.92 144.64 C90.72 91.41, 136.95 40.49, 166.92 2 M42.92 144.64 C66.95 114.58, 93.7 85.42, 166.92 2 M47.91 145 C75.02 114.35, 102.25 80.22, 171.91 2.36 M47.91 145 C87.4 99.39, 126.35 54.03, 171.91 2.36 M52.9 145.36 C79.04 113.6, 107.32 79.81, 177.55 1.96 M52.9 145.36 C77.61 115.94, 103.48 86, 177.55 1.96 M58.54 144.96 C92.12 105.3, 128.8 63.9, 182.54 2.32 M58.54 144.96 C106.61 90.51, 152.9 36.35, 182.54 2.32 M63.53 145.32 C107.47 99.27, 149.03 48.65, 188.18 1.93 M63.53 145.32 C96.13 109.61, 126.87 72.99, 188.18 1.93 M69.17 144.93 C102.47 104.11, 137.86 61.81, 193.17 2.29 M69.17 144.93 C110.04 98.38, 152.23 51.84, 193.17 2.29 M74.16 145.29 C104.78 108.19, 138.63 71.58, 198.81 1.9 M74.16 145.29 C99.31 117.03, 123.84 87.71, 198.81 1.9 M79.8 144.9 C130.71 87.32, 178.54 30.53, 203.8 2.26 M79.8 144.9 C124.29 94.42, 167.51 43.91, 203.8 2.26 M84.79 145.26 C111.41 111.48, 143.37 80.45, 209.44 1.86 M84.79 145.26 C130.36 93.05, 176.97 39.33, 209.44 1.86 M90.43 144.86 C128.08 97.62, 168.49 56.08, 214.43 2.22 M90.43 144.86 C130.35 96.04, 171.08 48.72, 214.43 2.22 M95.42 145.22 C146.19 90.39, 192.4 31.56, 220.07 1.83 M95.42 145.22 C120.72 114.85, 146.9 84.4, 220.07 1.83 M101.06 144.83 C127.22 111.52, 152.8 83.04, 225.06 2.19 M101.06 144.83 C132.86 108.13, 163.34 71.68, 225.06 2.19 M106.05 145.19 C154.17 92.56, 198.8 39.31, 230.7 1.79 M106.05 145.19 C136.53 110.59, 166.08 76.75, 230.7 1.79 M111.69 144.79 C138.65 117.15, 162.06 85.81, 235.69 2.15 M111.69 144.79 C137.61 113.65, 164.22 81.98, 235.69 2.15 M116.68 145.15 C157.06 99.25, 194.58 56.02, 241.33 1.76 M116.68 145.15 C155.1 99.61, 194.87 53.74, 241.33 1.76 M122.32 144.76 C155.38 104.12, 188.9 66.56, 246.32 2.12 M122.32 144.76 C171.19 89.94, 218.89 34.94, 246.32 2.12 M127.31 145.12 C160.63 107.75, 195.41 68.39, 251.96 1.72 M127.31 145.12 C171.79 94.11, 216.13 43.25, 251.96 1.72 M132.95 144.72 C167.14 104.52, 206.3 64.21, 256.95 2.08 M132.95 144.72 C164.99 105.52, 198.48 68.92, 256.95 2.08 M137.94 145.08 C183.71 92.31, 226.45 45.7, 262.59 1.69 M137.94 145.08 C163.89 115.91, 190.22 85.14, 262.59 1.69 M143.58 144.69 C174.92 109.48, 206.89 70.46, 267.58 2.05 M143.58 144.69 C170.4 113.21, 199.74 82.17, 267.58 2.05 M148.57 145.05 C178.08 111.76, 203.33 81.73, 272.56 2.41 M148.57 145.05 C173.64 115.53, 198.59 86.39, 272.56 2.41 M154.21 144.65 C187.55 108.07, 219.55 68.66, 278.21 2.01 M154.21 144.65 C200.06 91.97, 243.47 39.89, 278.21 2.01 M159.2 145.01 C186.66 112.8, 212.53 85.21, 283.19 2.37 M159.2 145.01 C207.12 89.92, 255.48 37.14, 283.19 2.37 M164.19 145.37 C202.12 104.92, 237.2 59.79, 288.84 1.98 M164.19 145.37 C194.82 106.77, 226.84 70.45, 288.84 1.98 M169.83 144.98 C212.12 93.05, 258.35 43.46, 293.82 2.34 M169.83 144.98 C201.44 106.46, 235.81 67.61, 293.82 2.34 M174.82 145.34 C207.54 111.65, 235.58 74.76, 299.47 1.94 M174.82 145.34 C199.92 118.44, 225.68 88.79, 299.47 1.94 M180.46 144.94 C208.5 116.42, 230.68 85.13, 304.45 2.3 M180.46 144.94 C218.97 100.78, 259.51 55.75, 304.45 2.3 M185.45 145.31 C221.24 102.72, 256.86 59.74, 310.1 1.91 M185.45 145.31 C232.8 92.21, 280.33 37.72, 310.1 1.91 M191.09 144.91 C238.27 88.19, 288.2 35.33, 315.08 2.27 M191.09 144.91 C220.59 109.55, 252.22 74.41, 315.08 2.27 M196.08 145.27 C239.76 94.85, 284.75 46.35, 320.73 1.88 M196.08 145.27 C230.49 104.94, 265.25 68.33, 320.73 1.88 M201.72 144.88 C234.66 108.79, 263.22 71.79, 325.71 2.24 M201.72 144.88 C237.32 101.42, 273.7 59.07, 325.71 2.24 M206.71 145.24 C253.21 90.64, 300.99 37.76, 331.36 1.84 M206.71 145.24 C240.65 108.22, 273.71 69.33, 331.36 1.84 M212.35 144.84 C242.19 114.5, 267.67 79.17, 336.34 2.2 M212.35 144.84 C236.6 115.07, 262.16 86.57, 336.34 2.2 M217.34 145.2 C251.25 105.29, 286.42 64.08, 341.99 1.81 M217.34 145.2 C249.04 108.05, 278.75 71.6, 341.99 1.81 M222.98 144.81 C254.78 109.71, 284.95 73.51, 346.97 2.17 M222.98 144.81 C251.44 112.17, 277.38 81.44, 346.97 2.17 M227.97 145.17 C258.67 109.25, 290.39 71.64, 352.62 1.77 M227.97 145.17 C253.46 115.85, 279.91 85.09, 352.62 1.77 M233.61 144.77 C279.92 90.22, 328.36 36.09, 357.6 2.13 M233.61 144.77 C263.54 109.84, 296.58 72.44, 357.6 2.13 M238.6 145.13 C284.33 94.02, 329.86 38.18, 363.25 1.74 M238.6 145.13 C270.6 110.97, 300.1 77.3, 363.25 1.74 M244.24 144.74 C285.03 98.47, 325.99 49.52, 368.23 2.1 M244.24 144.74 C288.81 94.28, 333.65 43.13, 368.23 2.1 M249.23 145.1 C274.07 113.24, 304.32 83.42, 373.22 2.46 M249.23 145.1 C288.32 102.57, 326.71 58.15, 373.22 2.46 M254.87 144.7 C297.9 94.29, 343.78 43.11, 377.55 3.57 M254.87 144.7 C286.66 106.11, 320.39 66.72, 377.55 3.57 M259.86 145.06 C295.8 103.74, 331.06 61.72, 381.23 5.44 M259.86 145.06 C298.82 101.67, 336.82 56.01, 381.23 5.44 M265.5 144.67 C313.19 90.52, 361.44 33.85, 384.9 7.31 M265.5 144.67 C292.92 114.87, 319.46 83.51, 384.9 7.31 M270.49 145.03 C293.76 118.67, 317.19 90.91, 387.92 9.94 M270.49 145.03 C298.6 112.33, 327.42 78.58, 387.92 9.94 M276.13 144.63 C309.18 107.62, 339.56 70.71, 390.28 13.32 M276.13 144.63 C314.84 98.86, 353.06 54.55, 390.28 13.32 M281.12 144.99 C305.94 116.7, 328.37 88.76, 391.99 17.45 M281.12 144.99 C321.03 97.29, 362.22 51.33, 391.99 17.45 M286.1 145.35 C324.81 103.13, 365.35 57.02, 393.04 22.34 M286.1 145.35 C313.97 112.26, 343.79 77.31, 393.04 22.34 M291.75 144.96 C324.16 107.97, 358.78 71.44, 395.4 25.72 M291.75 144.96 C322.28 110.33, 352.3 74.13, 395.4 25.72 M296.73 145.32 C322.53 113.82, 350.61 86.17, 395.14 32.11 M296.73 145.32 C330.9 104.68, 367.32 64.68, 395.14 32.11 M302.38 144.93 C320.38 122.95, 340.8 101.95, 395.54 37.76 M302.38 144.93 C328.33 112.21, 356.43 82.25, 395.54 37.76 M307.36 145.29 C337.92 110.64, 366.55 76.31, 395.28 44.15 M307.36 145.29 C325.37 123.43, 344.42 100.44, 395.28 44.15 M313.01 144.89 C335.45 120.95, 355.71 93.18, 395.01 50.55 M313.01 144.89 C342.22 110.33, 372.53 76.59, 395.01 50.55 M317.99 145.25 C335.46 125.76, 350.14 107.98, 395.41 56.2 M317.99 145.25 C334.45 126.54, 349.07 108.69, 395.41 56.2 M323.64 144.86 C350.88 112.04, 380.82 81.41, 395.15 62.59 M323.64 144.86 C349.73 114.44, 376.12 84.65, 395.15 62.59 M328.62 145.22 C342.07 126.15, 358.11 110, 395.54 68.24 M328.62 145.22 C352.38 118.21, 373.64 91.69, 395.54 68.24 M334.27 144.82 C356.58 116.53, 380.06 91.2, 395.28 74.63 M334.27 144.82 C352.85 123.85, 373.24 101.61, 395.28 74.63 M339.25 145.18 C350.58 130.51, 366.65 115.79, 395.67 80.28 M339.25 145.18 C360.84 120.94, 381.57 97.17, 395.67 80.28 M344.9 144.79 C359.95 129.93, 374.97 110.51, 395.41 86.68 M344.9 144.79 C359.66 128.95, 373.84 112.99, 395.41 86.68 M349.88 145.15 C365.74 124.35, 384.39 105.92, 395.81 92.32 M349.88 145.15 C365.01 128.41, 378.61 111.44, 395.81 92.32 M355.53 144.75 C366.83 128.33, 381.85 115.58, 395.55 98.72 M355.53 144.75 C366.51 134.09, 376.57 121.06, 395.55 98.72 M360.51 145.11 C371.57 133.46, 379.69 124.34, 395.94 104.36 M360.51 145.11 C371.82 131.23, 384.33 117.41, 395.94 104.36 M364.19 146.98 C372.36 139.23, 375.92 131.75, 395.68 110.76 M364.19 146.98 C375.25 134.47, 383.71 124.14, 395.68 110.76 M370.49 145.83 C377.43 139.68, 383.18 129.21, 396.73 115.65 M370.49 145.83 C377.33 137.24, 382.58 130.01, 396.73 115.65 M377.44 143.93 C380.98 139.22, 385.26 134.38, 391.88 127.33 M377.44 143.93 C381.01 139.93, 385.77 134.2, 391.88 127.33" stroke="#ffec99" stroke-width="0.5" fill="none"></path><path d="M32 0 M32 0 C158.41 -0.65, 283.84 1.37, 362 0 M32 0 C99.31 -2.94, 165.01 -1.66, 362 0 M362 0 C382.37 -1.19, 392.06 11.27, 394 32 M362 0 C383.53 0.55, 395.84 12.5, 394 32 M394 32 C396.17 49.29, 394.95 72.1, 394 113 M394 32 C394.74 57.51, 393.79 80.56, 394 113 M394 113 C393.49 135.83, 381.87 143.9, 362 145 M394 113 C391.98 135.04, 383.6 143.19, 362 145 M362 145 C291.31 144.27, 221.15 142.78, 32 145 M362 145 C249.93 143.31, 138.97 142.96, 32 145 M32 145 C8.71 146.69, -1.86 135.95, 0 113 M32 145 C10.21 143.8, 1.65 132.18, 0 113 M0 113 C0.55 94.72, -1.35 75.26, 0 32 M0 113 C-0.88 90.36, -1.5 66.27, 0 32 M0 32 C1.9 12.2, 9.12 -1.48, 32 0 M0 32 C0.22 11.69, 10.1 1.96, 32 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(288.875 73.3828125) rotate(0 140.625 67.19999999999999)"><text x="0" y="0" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">directories: []</text><text x="0" y="19.2" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">files:</text><text x="0" y="38.4" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge"> - name: .keep</text><text x="0" y="57.599999999999994" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">   digest: &lt;empty-blob-digest&gt;</text><text x="0" y="76.8" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">   size: 0</text><text x="0" y="96" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">   executable: false</text><text x="0" y="115.19999999999999" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">symlinks: []</text></g><g stroke-linecap="round" transform="translate(279.59193843887 271.159696266393) rotate(0 198.99999999999994 130)"><path d="M7.86 8.16 C7.86 8.16, 7.86 8.16, 7.86 8.16 M7.86 8.16 C7.86 8.16, 7.86 8.16, 7.86 8.16 M3.66 19.09 C7.06 13.81, 9.24 9.34, 18.1 2.48 M3.66 19.09 C8.44 11.73, 13.76 5.27, 18.1 2.48 M2.75 26.24 C10 16.18, 18.61 6.13, 24.4 1.33 M2.75 26.24 C9.44 19.31, 14.57 12.4, 24.4 1.33 M2.48 32.64 C10.34 21.03, 17.74 12.3, 30.7 0.18 M2.48 32.64 C13.49 21.44, 21.83 9.48, 30.7 0.18 M2.88 38.28 C12.91 25, 26.86 13.76, 34.37 2.05 M2.88 38.28 C12.64 28.15, 21.63 16.63, 34.37 2.05 M2.62 44.68 C14.17 33.27, 22.89 18.23, 39.36 2.41 M2.62 44.68 C12.44 34.48, 21.24 23.55, 39.36 2.41 M2.36 51.07 C18.27 36.19, 31.27 19.8, 45 2.02 M2.36 51.07 C15.34 37.29, 27.12 23.41, 45 2.02 M2.75 56.72 C18.22 37.56, 35.93 17.94, 49.99 2.38 M2.75 56.72 C20.57 34.56, 37.8 14.24, 49.99 2.38 M2.49 63.11 C18.51 41.78, 36.66 23.69, 55.63 1.98 M2.49 63.11 C16.06 46.84, 30.13 32.58, 55.63 1.98 M2.23 69.51 C14.08 54.4, 27.81 42.3, 60.62 2.34 M2.23 69.51 C14.8 54.54, 28.11 38.47, 60.62 2.34 M2.62 75.16 C23.22 53.14, 45.11 28.68, 66.26 1.95 M2.62 75.16 C18.68 55.49, 35.76 36.87, 66.26 1.95 M2.36 81.55 C29.49 51.06, 52.67 20.07, 71.25 2.31 M2.36 81.55 C23.35 59.17, 43.86 35.46, 71.25 2.31 M2.76 87.2 C29.08 59.14, 54.26 30.38, 76.89 1.91 M2.76 87.2 C25.2 61.93, 47.39 35.45, 76.89 1.91 M2.49 93.59 C22.97 73.16, 41.24 50.49, 81.88 2.27 M2.49 93.59 C22.85 70.13, 41.2 48.51, 81.88 2.27 M2.23 99.99 C26.73 70.85, 53 42.34, 87.52 1.88 M2.23 99.99 C26.11 70.65, 52.18 43.37, 87.52 1.88 M2.63 105.64 C29.25 73.43, 57.28 44.11, 92.51 2.24 M2.63 105.64 C22.21 84.02, 40.09 61.9, 92.51 2.24 M2.37 112.03 C24.78 84.92, 46.32 60.61, 98.15 1.85 M2.37 112.03 C40.71 68.73, 77.43 25.35, 98.15 1.85 M2.1 118.43 C32.45 81.76, 64.34 45.79, 103.14 2.21 M2.1 118.43 C26.04 90.13, 50.16 63.23, 103.14 2.21 M2.5 124.07 C34.35 88.85, 66.58 53.31, 108.78 1.81 M2.5 124.07 C35.8 86.3, 67.38 48.42, 108.78 1.81 M2.24 130.47 C44.13 79.64, 85.07 32.68, 113.77 2.17 M2.24 130.47 C28.26 100.65, 54.41 68.41, 113.77 2.17 M2.63 136.11 C39.83 96.08, 73.86 52.5, 119.41 1.78 M2.63 136.11 C27.92 105.97, 55.2 76.2, 119.41 1.78 M2.37 142.51 C38.64 103.62, 72.54 62.27, 124.4 2.14 M2.37 142.51 C38.27 104.7, 71.42 64.68, 124.4 2.14 M2.11 148.91 C31.62 115.14, 60.21 82.46, 130.04 1.74 M2.11 148.91 C30.64 115.22, 60.37 82.49, 130.04 1.74 M2.5 154.55 C45.43 105.72, 87.93 57.79, 135.03 2.1 M2.5 154.55 C39.77 111.59, 78.21 68.92, 135.03 2.1 M2.24 160.95 C40.01 120.72, 75.93 77.72, 140.67 1.71 M2.24 160.95 C44.66 112.93, 87.03 64.56, 140.67 1.71 M1.98 167.35 C39.14 124.53, 74.75 81.57, 145.66 2.07 M1.98 167.35 C47.85 117.91, 91.41 66.21, 145.66 2.07 M2.38 172.99 C58.38 106.95, 115.31 43.43, 150.65 2.43 M2.38 172.99 C53.11 115.34, 104.81 56.32, 150.65 2.43 M2.11 179.39 C64.27 108.42, 124.58 39.52, 156.29 2.03 M2.11 179.39 C42.26 131.15, 83.61 83.43, 156.29 2.03 M2.51 185.03 C62.42 116.85, 122.68 43.77, 161.28 2.39 M2.51 185.03 C56.66 122.43, 112.32 58.31, 161.28 2.39 M2.25 191.43 C51.58 133.03, 102.03 75.72, 166.92 2 M2.25 191.43 C38.3 149.45, 75.45 107.32, 166.92 2 M1.99 197.83 C42.68 152.88, 80.66 108.12, 171.91 2.36 M1.99 197.83 C66.15 123.85, 132.83 47.33, 171.91 2.36 M2.38 203.47 C55.91 143.98, 107.7 83.76, 177.55 1.96 M2.38 203.47 C56.81 142.73, 110.46 81.58, 177.55 1.96 M2.12 209.87 C66.55 137, 131.82 62.14, 182.54 2.32 M2.12 209.87 C50.61 157.03, 98.73 101.44, 182.54 2.32 M1.86 216.27 C63.05 142.96, 126.25 69.69, 188.18 1.93 M1.86 216.27 C65.44 140.95, 131.03 66.87, 188.18 1.93 M2.25 221.91 C59.05 161.57, 111.38 97.22, 193.17 2.29 M2.25 221.91 C52.49 164.23, 101.5 106.36, 193.17 2.29 M1.33 229.06 C64.26 156.57, 124.34 84.79, 198.81 1.9 M1.33 229.06 C60.97 162.1, 120.98 94.62, 198.81 1.9 M1.73 234.71 C54.24 174.7, 106.54 116.97, 203.8 2.26 M1.73 234.71 C76.5 149.79, 152.59 63.83, 203.8 2.26 M2.78 239.59 C49.77 186.82, 94.95 134.94, 209.44 1.86 M2.78 239.59 C83.35 146.44, 163.63 53.44, 209.44 1.86 M4.49 243.73 C48.55 190.47, 93.86 139.53, 214.43 2.22 M4.49 243.73 C63.53 174.61, 122.23 106.2, 214.43 2.22 M6.19 247.86 C77.48 165.73, 149.04 81.52, 220.07 1.83 M6.19 247.86 C85.41 157.55, 164.08 66.86, 220.07 1.83 M8.56 251.24 C90.22 155.68, 175.16 56.51, 225.06 2.19 M8.56 251.24 C89.12 157.29, 172.43 62.13, 225.06 2.19 M10.92 254.62 C69.41 191.54, 124.13 126, 230.7 1.79 M10.92 254.62 C69.41 187.27, 127.03 120.42, 230.7 1.79 M13.94 257.24 C82.45 176.73, 153.61 97.39, 235.69 2.15 M13.94 257.24 C93.45 166.82, 173.91 75.69, 235.69 2.15 M18.27 258.36 C76.98 194.1, 131.53 129.14, 241.33 1.76 M18.27 258.36 C66.67 201.61, 115.46 145.37, 241.33 1.76 M22.6 259.47 C75.95 201, 128.47 140.06, 246.32 2.12 M22.6 259.47 C70.35 207.82, 116.17 155.22, 246.32 2.12 M26.27 261.34 C95.93 180.59, 169.95 95.39, 251.96 1.72 M26.27 261.34 C89.37 190.53, 152.1 118.22, 251.96 1.72 M32.57 260.19 C96.06 189.84, 155.98 120.97, 256.95 2.08 M32.57 260.19 C78.18 205.29, 124.7 151.48, 256.95 2.08 M37.56 260.55 C117 171.22, 192.42 83.99, 262.59 1.69 M37.56 260.55 C121.96 163.6, 206.2 67.19, 262.59 1.69 M43.2 260.16 C105.28 189.09, 169.45 117.92, 267.58 2.05 M43.2 260.16 C123.84 168.01, 203.45 76.43, 267.58 2.05 M48.19 260.52 C117.81 178.99, 186.9 100.6, 272.56 2.41 M48.19 260.52 C125.53 171.84, 202.4 82.45, 272.56 2.41 M53.83 260.12 C138.08 162.28, 222.76 67.81, 278.21 2.01 M53.83 260.12 C128.55 171.85, 204.35 84.41, 278.21 2.01 M58.82 260.48 C127.06 184.42, 193 106.58, 283.19 2.37 M58.82 260.48 C135.91 173.81, 211.1 87.23, 283.19 2.37 M64.46 260.09 C109.71 206.88, 156.75 154.77, 288.84 1.98 M64.46 260.09 C142.84 169.53, 220.7 80.3, 288.84 1.98 M69.45 260.45 C138.09 183.73, 208 105.35, 293.82 2.34 M69.45 260.45 C156.6 162.77, 241.99 64.65, 293.82 2.34 M75.09 260.06 C143.01 182.6, 211.21 103.49, 299.47 1.94 M75.09 260.06 C136 192, 195.87 123.37, 299.47 1.94 M80.08 260.42 C126.17 206.79, 172.95 151.57, 304.45 2.3 M80.08 260.42 C129.25 204.69, 179.83 146.37, 304.45 2.3 M85.72 260.02 C157.76 178.28, 226.22 97.47, 310.1 1.91 M85.72 260.02 C149.05 184.68, 213.67 111.24, 310.1 1.91 M90.71 260.38 C181.48 157.9, 269.25 55.91, 315.08 2.27 M90.71 260.38 C157.59 183.34, 224.55 106.5, 315.08 2.27 M95.7 260.74 C174.5 168.64, 255.03 76.7, 320.73 1.88 M95.7 260.74 C164.52 183.36, 232.23 105.12, 320.73 1.88 M101.34 260.35 C181.6 167.21, 261.15 73.9, 325.71 2.24 M101.34 260.35 C177.77 172.29, 255.39 82.95, 325.71 2.24 M106.33 260.71 C168.93 189.98, 230.06 115.81, 331.36 1.84 M106.33 260.71 C170.53 184.03, 237.17 107.92, 331.36 1.84 M111.97 260.31 C176.35 190.63, 236.85 119.43, 336.34 2.2 M111.97 260.31 C157.57 208.21, 204.38 154.93, 336.34 2.2 M116.96 260.67 C164.3 205.68, 212.63 148.72, 341.99 1.81 M116.96 260.67 C197.94 169.46, 278.58 77.5, 341.99 1.81 M122.6 260.28 C175.27 200.79, 229.84 138.31, 346.97 2.17 M122.6 260.28 C172.5 202.83, 220.76 146.35, 346.97 2.17 M127.59 260.64 C215.15 158.17, 304.42 54.77, 352.62 1.77 M127.59 260.64 C188.96 189.82, 250.22 118.28, 352.62 1.77 M133.23 260.24 C183.32 201.93, 235.94 141.72, 357.6 2.13 M133.23 260.24 C184.28 203.09, 233.15 145.34, 357.6 2.13 M138.22 260.6 C220.44 165.6, 301.95 72.93, 363.25 1.74 M138.22 260.6 C215.16 172.58, 291.69 83.91, 363.25 1.74 M143.86 260.21 C216.13 172.96, 292.05 87.04, 368.23 2.1 M143.86 260.21 C223.54 169.38, 302.36 80.03, 368.23 2.1 M148.85 260.57 C228.87 169.81, 309.22 79.04, 374.53 0.95 M148.85 260.57 C225.85 169.3, 304.04 78.63, 374.53 0.95 M154.49 260.17 C230.89 172.65, 306.45 86.57, 378.21 2.82 M154.49 260.17 C210.94 195.8, 265.6 130.92, 378.21 2.82 M159.48 260.53 C223.94 184.22, 290.75 110.57, 382.54 3.93 M159.48 260.53 C244.05 163.85, 329.27 67.22, 382.54 3.93 M165.12 260.14 C236.95 176.83, 309.56 92.2, 386.87 5.05 M165.12 260.14 C221.23 195.95, 279.16 130.62, 386.87 5.05 M170.11 260.5 C221.21 201.49, 273.24 142.28, 389.23 8.43 M170.11 260.5 C218.37 205.87, 266.44 150.41, 389.23 8.43 M175.75 260.11 C241.58 187.82, 304.45 113.61, 392.25 11.05 M175.75 260.11 C222.69 207.98, 267.39 155.95, 392.25 11.05 M180.74 260.47 C245.33 188.58, 307.54 115.78, 395.27 13.68 M180.74 260.47 C261.6 168.2, 340.34 76.88, 395.27 13.68 M186.38 260.07 C251.57 186.65, 319.28 107.6, 396.32 18.56 M186.38 260.07 C236.65 200.09, 289.45 140.81, 396.32 18.56 M191.37 260.43 C255.8 183.29, 323.42 107.08, 397.37 23.45 M191.37 260.43 C233.86 209.7, 277.09 160.88, 397.37 23.45 M197.01 260.04 C273.9 173.76, 350.14 83.39, 397.77 29.09 M197.01 260.04 C238.49 210.28, 280.91 161.3, 397.77 29.09 M202 260.4 C249.95 204.58, 301.21 145.35, 399.47 33.23 M202 260.4 C246.27 207.11, 292.1 154.9, 399.47 33.23 M207.64 260 C282.04 175.37, 353.03 93.88, 399.21 39.63 M207.64 260 C252.23 206.89, 298.22 154.54, 399.21 39.63 M212.63 260.36 C270.79 190.87, 332.59 125.04, 399.61 45.27 M212.63 260.36 C255.76 212.2, 297.66 164.88, 399.61 45.27 M217.62 260.72 C270.87 200.01, 324.39 137.32, 399.34 51.67 M217.62 260.72 C286.2 182.97, 352.23 105.9, 399.34 51.67 M223.26 260.33 C266.88 209.47, 311.98 157.05, 399.74 57.31 M223.26 260.33 C270.48 203.33, 319.36 147.73, 399.74 57.31 M228.25 260.69 C264.48 217.85, 298.81 176.68, 399.48 63.71 M228.25 260.69 C287.06 192.75, 345.09 124.31, 399.48 63.71 M233.89 260.29 C283.82 202.81, 333.35 147.36, 399.22 70.11 M233.89 260.29 C296.32 189.36, 357.95 117.97, 399.22 70.11 M238.88 260.65 C288.24 201.51, 340.4 144.75, 399.61 75.75 M238.88 260.65 C302.98 187.65, 365.83 115.51, 399.61 75.75 M244.52 260.26 C301.41 192.57, 362.82 123.97, 399.35 82.15 M244.52 260.26 C276.87 221.94, 309.71 183.55, 399.35 82.15 M249.51 260.62 C294.86 210.74, 338.19 159.76, 399.74 87.79 M249.51 260.62 C288.68 213.68, 329.48 168.33, 399.74 87.79 M255.15 260.22 C306.36 203.12, 357.73 144.26, 399.48 94.19 M255.15 260.22 C310.1 197.2, 364.53 133.07, 399.48 94.19 M260.14 260.58 C306.6 208.36, 353.31 154.47, 399.22 100.59 M260.14 260.58 C314.84 198.11, 370.53 134.35, 399.22 100.59 M265.78 260.19 C310.07 209.18, 353.85 155.07, 399.62 106.23 M265.78 260.19 C313.52 203.42, 361.67 148.41, 399.62 106.23 M270.77 260.55 C312.94 210.71, 350.92 165.03, 399.35 112.63 M270.77 260.55 C313.74 212.09, 354.22 165.25, 399.35 112.63 M276.41 260.15 C322.47 205.94, 367.79 155.3, 399.75 118.27 M276.41 260.15 C319.24 210.97, 362.85 162.61, 399.75 118.27 M281.4 260.51 C309.82 228.04, 337.57 195, 399.49 124.67 M281.4 260.51 C315.37 218.59, 351.47 180.05, 399.49 124.67 M287.04 260.12 C329.88 210.1, 376.19 158.03, 399.23 131.06 M287.04 260.12 C314.45 229.24, 339.01 200.91, 399.23 131.06 M292.03 260.48 C332.66 211.87, 372.61 167.01, 399.62 136.71 M292.03 260.48 C317.01 231.17, 342.37 202.7, 399.62 136.71 M297.67 260.09 C320.79 233.82, 342.65 206.24, 399.36 143.11 M297.67 260.09 C337.01 212.55, 377.78 167.34, 399.36 143.11 M302.66 260.45 C335.5 224.45, 365.99 190.46, 399.75 148.75 M302.66 260.45 C341.03 214.95, 379.75 172.24, 399.75 148.75 M308.3 260.05 C331.34 234.81, 350.27 209.28, 399.49 155.15 M308.3 260.05 C337.96 226.28, 369 190.93, 399.49 155.15 M313.29 260.41 C344.16 226.42, 373.26 191.65, 399.23 161.54 M313.29 260.41 C340.87 229.56, 366.67 199.76, 399.23 161.54 M318.93 260.02 C339.08 235.8, 357.34 215.88, 399.63 167.19 M318.93 260.02 C338.18 238.19, 356.44 217.12, 399.63 167.19 M323.92 260.38 C343.38 240.84, 359.55 216.05, 399.36 173.59 M323.92 260.38 C339.58 242.35, 357.05 223.74, 399.36 173.59 M328.9 260.74 C356.6 230.93, 381.38 201.75, 399.76 179.23 M328.9 260.74 C355.35 229.48, 382.07 199.6, 399.76 179.23 M334.55 260.34 C353.55 235.84, 374.53 215.15, 399.5 185.63 M334.55 260.34 C350.96 240.58, 366.19 222.61, 399.5 185.63 M339.53 260.7 C355.36 243.43, 370.27 221.73, 399.24 192.02 M339.53 260.7 C361.66 234.82, 383.39 210.67, 399.24 192.02 M345.18 260.31 C362.54 238.6, 380.33 220.43, 399.63 197.67 M345.18 260.31 C359.44 243.81, 374.08 228.32, 399.63 197.67 M350.16 260.67 C364.5 243.76, 376.71 230.97, 399.37 204.06 M350.16 260.67 C363.93 245.32, 377.91 229.24, 399.37 204.06 M355.81 260.27 C372.91 242.23, 387.92 223.73, 399.76 209.71 M355.81 260.27 C373.85 240.57, 389.06 220.82, 399.76 209.71 M360.79 260.63 C368.09 252.39, 377.96 242.27, 399.5 216.11 M360.79 260.63 C372.53 247.08, 385.83 232.19, 399.5 216.11 M366.44 260.24 C372.92 252.64, 384.19 241.59, 399.24 222.5 M366.44 260.24 C374.31 251.44, 384.03 240.28, 399.24 222.5 M372.74 259.09 C379.45 251.45, 383.63 244.65, 398.98 228.9 M372.74 259.09 C383.24 247.41, 391.87 236.99, 398.98 228.9 M378.38 258.69 C382.28 255.84, 387.86 247.21, 398.06 236.05 M378.38 258.69 C382.66 254.99, 386.79 248.97, 398.06 236.05 M385.99 256.04 C389.48 251.84, 392.18 249.55, 394.52 246.23 M385.99 256.04 C389.44 252.25, 392.29 247.67, 394.52 246.23" stroke="#ffec99" stroke-width="0.5" fill="none"></path><path d="M32 0 M32 0 C123.14 1.36, 217.94 1.27, 366 0 M32 0 C131.29 -1.79, 231.25 -1.6, 366 0 M366 0 C387.8 0.82, 396.14 9, 398 32 M366 0 C388.4 -1.28, 396.3 11.05, 398 32 M398 32 C396.68 77, 397.3 120.08, 398 228 M398 32 C397.91 99.78, 396.94 166.68, 398 228 M398 228 C399.84 247.36, 385.48 260.38, 366 260 M398 228 C399.48 250.74, 389.16 259.77, 366 260 M366 260 C278.41 260.83, 191.33 259.2, 32 260 M366 260 C254.85 261.48, 142.72 261.51, 32 260 M32 260 C9.06 261.46, -1.27 247.64, 0 228 M32 260 C8.96 260.25, 0.79 249.13, 0 228 M0 228 C-2.62 187.23, -0.98 150.02, 0 32 M0 228 C-1.12 169.92, -1.74 111.79, 0 32 M0 32 C-0.23 11.83, 11.85 0.53, 32 0 M0 32 C1.74 9.13, 11.06 1.81, 32 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(284.59193843887 276.159696266393) rotate(0 182.81249999999994 124.80000000000001)"><text x="0" y="0" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">directories:</text><text x="0" y="19.2" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge"> - name: keep</text><text x="0" y="38.4" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">   digest: &lt;directory-with-keep-digest&gt;</text><text x="0" y="57.599999999999994" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">   size: 1</text><text x="0" y="76.8" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">   executable: false</text><text x="0" y="96" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">files:</text><text x="0" y="115.19999999999999" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge"> - name: .keep</text><text x="0" y="134.4" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">   digest: &lt;empty-blob-digest&gt;</text><text x="0" y="153.6" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">   size: 0</text><text x="0" y="172.79999999999998" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">   executable: false</text><text x="0" y="192" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">symlinks:</text><text x="0" y="211.2" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge"> - name: aa</text><text x="0" y="230.39999999999998" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">   target: /nix/store/somewhereelse</text></g><g stroke-linecap="round" transform="translate(292.58984375 581.578125) rotate(0 192 36.5)"><path d="M4.86 4.22 C4.86 4.22, 4.86 4.22, 4.86 4.22 M4.86 4.22 C4.86 4.22, 4.86 4.22, 4.86 4.22 M3.28 12.13 C5.02 8.82, 8.25 4.62, 11.81 2.32 M3.28 12.13 C6.19 8.3, 9.57 5.6, 11.81 2.32 M1.71 20.04 C8.34 16.13, 10.93 7.61, 18.77 0.41 M1.71 20.04 C6.51 15.46, 9.85 9.62, 18.77 0.41 M2.11 25.68 C10.79 16.53, 14.3 8.85, 23.76 0.77 M2.11 25.68 C8.12 18.52, 15.95 11.63, 23.76 0.77 M1.84 32.08 C8.84 22.24, 16.07 17.29, 29.4 0.38 M1.84 32.08 C11.36 20.47, 23.16 9.07, 29.4 0.38 M2.24 37.72 C9.27 29.77, 15.4 22.85, 34.39 0.74 M2.24 37.72 C15.42 23.1, 27.12 8.74, 34.39 0.74 M2.63 43.36 C8.76 33.58, 17 25.79, 40.03 0.35 M2.63 43.36 C13.14 31.06, 23.17 18.26, 40.03 0.35 M2.37 49.76 C16.36 34.07, 29.4 16.32, 45.02 0.71 M2.37 49.76 C17.93 32.43, 33.93 13.74, 45.02 0.71 M2.77 55.41 C19.05 35.42, 38.7 10.68, 50.66 0.31 M2.77 55.41 C19.96 34.91, 39.43 13.51, 50.66 0.31 M1.19 63.31 C17.21 49, 29.42 31.12, 55.65 0.67 M1.19 63.31 C15.9 46.95, 29.88 30.05, 55.65 0.67 M4.21 65.94 C21.41 44.81, 41.02 26.02, 61.29 0.28 M4.21 65.94 C24.28 42.91, 45.96 20.08, 61.29 0.28 M6.57 69.32 C23.78 51.2, 36.27 33.53, 66.28 0.64 M6.57 69.32 C19.35 53.63, 31.93 38.99, 66.28 0.64 M9.59 71.94 C25.17 56.48, 40.49 38.06, 71.92 0.24 M9.59 71.94 C23.84 58.2, 36.53 43.78, 71.92 0.24 M13.92 73.05 C31.98 52.05, 54.85 25.68, 76.91 0.6 M13.92 73.05 C30.95 53.92, 48.62 33.36, 76.91 0.6 M19.57 72.66 C38.35 51.77, 53.43 34.49, 82.55 0.21 M19.57 72.66 C32.02 56.9, 44.97 41.55, 82.55 0.21 M24.55 73.02 C48.11 46.73, 66.96 24.34, 87.54 0.57 M24.55 73.02 C47.62 46.37, 71.59 19.54, 87.54 0.57 M30.2 72.63 C46.25 51.67, 67.22 31.55, 93.18 0.17 M30.2 72.63 C53.38 46.94, 75.41 21.61, 93.18 0.17 M35.18 72.99 C55.38 48.59, 74.75 27.94, 98.17 0.53 M35.18 72.99 C57.1 47.59, 77.7 22.46, 98.17 0.53 M40.83 72.59 C64.45 43.72, 89.12 19.76, 103.81 0.14 M40.83 72.59 C61.26 47.84, 82.21 23.37, 103.81 0.14 M45.81 72.95 C66.24 52.79, 83.08 30.43, 108.8 0.5 M45.81 72.95 C67.8 48.81, 88.58 24.77, 108.8 0.5 M51.46 72.56 C64.09 57.9, 78.25 44.63, 113.78 0.86 M51.46 72.56 C72.83 47.42, 94.74 22.72, 113.78 0.86 M56.44 72.92 C74.65 52.58, 96.04 31.05, 119.43 0.46 M56.44 72.92 C81.73 45.16, 105.23 18.3, 119.43 0.46 M62.09 72.52 C80.07 51.93, 98.45 29.82, 124.41 0.82 M62.09 72.52 C79.04 54.05, 95.96 34.96, 124.41 0.82 M67.07 72.88 C80.12 59.08, 92.94 42.21, 130.06 0.43 M67.07 72.88 C80.66 58.14, 95.45 40.91, 130.06 0.43 M72.06 73.24 C93.79 49.66, 111.38 26.32, 135.04 0.79 M72.06 73.24 C89.58 51.72, 107.82 32.09, 135.04 0.79 M77.7 72.85 C104.72 44.35, 127.17 17, 140.69 0.4 M77.7 72.85 C96.13 51.38, 114.98 29.95, 140.69 0.4 M82.69 73.21 C104.71 47.57, 127.59 22.31, 145.67 0.76 M82.69 73.21 C101.71 51.47, 120.77 29.01, 145.67 0.76 M88.33 72.81 C110.76 47.24, 131.64 20.5, 151.32 0.36 M88.33 72.81 C109 48.14, 130.72 23.09, 151.32 0.36 M93.32 73.17 C111.15 55.01, 126.29 31.78, 156.3 0.72 M93.32 73.17 C110.49 51.39, 129.9 29.91, 156.3 0.72 M98.96 72.78 C118.26 54.28, 133.1 34.79, 161.95 0.33 M98.96 72.78 C111.06 59.03, 124.81 44.07, 161.95 0.33 M103.95 73.14 C115.72 58.92, 129.64 40.85, 166.93 0.69 M103.95 73.14 C126.22 47.2, 149.58 21.56, 166.93 0.69 M109.59 72.74 C123.31 56.85, 140.06 38.01, 172.58 0.29 M109.59 72.74 C124.38 56.56, 137.2 40.37, 172.58 0.29 M114.58 73.1 C138.07 45.66, 163.14 15.75, 177.56 0.65 M114.58 73.1 C131.86 52.94, 147.99 32.77, 177.56 0.65 M120.22 72.71 C133.34 57.46, 150.05 38.7, 183.21 0.26 M120.22 72.71 C135.3 57.12, 148.59 39.51, 183.21 0.26 M125.21 73.07 C147.35 45.64, 169.73 21.54, 188.19 0.62 M125.21 73.07 C146.83 48.56, 167.28 24.07, 188.19 0.62 M130.85 72.68 C149.9 47.27, 173.04 22.78, 193.84 0.22 M130.85 72.68 C153.31 46.45, 175.71 22.68, 193.84 0.22 M135.84 73.04 C157.75 47.97, 181.46 23.22, 198.82 0.58 M135.84 73.04 C157.01 47.37, 178.52 21.52, 198.82 0.58 M141.49 72.64 C163.6 47.54, 184.92 24.28, 203.81 0.94 M141.49 72.64 C157.97 55.11, 171.75 36.25, 203.81 0.94 M146.47 73 C164.74 50.07, 184.75 31.86, 209.45 0.55 M146.47 73 C169.77 45.07, 195.2 17.9, 209.45 0.55 M152.12 72.61 C170.98 50.38, 191.11 25.57, 214.44 0.91 M152.12 72.61 C167.16 54.48, 184.74 36.2, 214.44 0.91 M157.1 72.97 C171.7 55.53, 188.24 37.45, 220.08 0.51 M157.1 72.97 C171.53 57.7, 184.96 42.02, 220.08 0.51 M162.09 73.33 C182.79 52.34, 200.46 29.24, 225.07 0.87 M162.09 73.33 C176.54 57.94, 188.74 43.02, 225.07 0.87 M167.73 72.93 C187.78 52.57, 204.67 31.34, 230.71 0.48 M167.73 72.93 C192.31 45.48, 215.11 18.16, 230.71 0.48 M172.72 73.29 C192.03 53.03, 213.93 26.16, 235.7 0.84 M172.72 73.29 C187.14 54.62, 203.69 37.67, 235.7 0.84 M178.36 72.9 C196.89 49.98, 218.6 27.24, 241.34 0.45 M178.36 72.9 C191.33 57.21, 204.9 42.89, 241.34 0.45 M183.35 73.26 C207.11 47.48, 230.29 17.13, 246.33 0.81 M183.35 73.26 C195.69 56.95, 208.85 41.55, 246.33 0.81 M188.99 72.86 C203.94 55.74, 221.89 34.74, 251.97 0.41 M188.99 72.86 C203.18 55.08, 218.22 38.46, 251.97 0.41 M193.98 73.22 C219.55 44.07, 240.87 19.79, 256.96 0.77 M193.98 73.22 C208.25 55.94, 223.54 39.09, 256.96 0.77 M199.62 72.83 C217.99 48.42, 241.49 28.33, 262.6 0.38 M199.62 72.83 C214.61 56.41, 229.03 40.98, 262.6 0.38 M204.61 73.19 C223.33 53.31, 241.74 30.68, 267.59 0.74 M204.61 73.19 C229.17 46.22, 251.07 19.59, 267.59 0.74 M210.25 72.79 C224.49 54.46, 241.18 34.58, 273.23 0.34 M210.25 72.79 C227.21 52.37, 245.03 32.68, 273.23 0.34 M215.24 73.15 C229.43 57.1, 240.31 42.47, 278.22 0.7 M215.24 73.15 C237.32 48.52, 258.37 22.2, 278.22 0.7 M220.88 72.76 C240.5 49.7, 259.92 29.24, 283.86 0.31 M220.88 72.76 C245.23 46.31, 268.29 19.17, 283.86 0.31 M225.87 73.12 C244.42 48.72, 265.88 27.95, 288.85 0.67 M225.87 73.12 C251.66 44.25, 275.97 16.49, 288.85 0.67 M231.51 72.72 C253.24 45.13, 279.63 17.21, 293.84 1.03 M231.51 72.72 C244.39 57.29, 257.3 41.73, 293.84 1.03 M236.5 73.08 C256.68 52.52, 274.01 31.26, 299.48 0.63 M236.5 73.08 C252.49 52.88, 270.22 34.34, 299.48 0.63 M242.14 72.69 C264.56 48.95, 287.34 23.02, 304.47 0.99 M242.14 72.69 C265.99 45.72, 289.13 17.36, 304.47 0.99 M247.13 73.05 C268.76 49.79, 290 25.19, 310.11 0.6 M247.13 73.05 C271.93 45.31, 297.4 16.35, 310.11 0.6 M252.77 72.66 C272.81 49.72, 292.69 22.65, 315.1 0.96 M252.77 72.66 C274.82 46.14, 296.99 21.06, 315.1 0.96 M257.76 73.02 C279.06 47.65, 296.04 26.13, 320.74 0.56 M257.76 73.02 C279.13 48.78, 298.14 26.64, 320.74 0.56 M262.75 73.38 C286.03 44.74, 309.2 19.59, 325.73 0.92 M262.75 73.38 C284.29 48.03, 306.79 23.96, 325.73 0.92 M268.39 72.98 C284.12 55.13, 298.79 37.14, 331.37 0.53 M268.39 72.98 C286.16 50.17, 306.05 30.27, 331.37 0.53 M273.38 73.34 C296.56 45.22, 323.75 15.16, 336.36 0.89 M273.38 73.34 C289.13 55.59, 302.31 40.33, 336.36 0.89 M279.02 72.95 C303.53 43.87, 326.85 18.13, 342 0.49 M279.02 72.95 C293.37 55.49, 308.15 39.18, 342 0.49 M284.01 73.31 C298.42 57.71, 311.23 40.55, 346.99 0.85 M284.01 73.31 C308.02 43.63, 333.49 16.03, 346.99 0.85 M289.65 72.91 C311.51 49.12, 331.37 27.36, 352.63 0.46 M289.65 72.91 C314.28 43.1, 339.52 15.91, 352.63 0.46 M294.64 73.27 C311.15 55.94, 323.35 38.14, 357.62 0.82 M294.64 73.27 C315.12 50.08, 336.79 25.51, 357.62 0.82 M300.28 72.88 C323.32 47.78, 344.6 22, 363.26 0.43 M300.28 72.88 C320.59 50.02, 339.32 28.35, 363.26 0.43 M305.27 73.24 C321.19 53.97, 335.23 38.9, 368.25 0.79 M305.27 73.24 C320.38 55.99, 334.56 39.62, 368.25 0.79 M310.91 72.84 C326.89 57.23, 339.69 36.32, 372.58 1.9 M310.91 72.84 C323.53 58.18, 338.06 42.96, 372.58 1.9 M315.9 73.2 C339.86 47.6, 361.01 22.6, 376.91 3.02 M315.9 73.2 C338.69 46.17, 361.68 20.58, 376.91 3.02 M321.54 72.81 C338.51 50.61, 357.46 32.27, 379.93 5.64 M321.54 72.81 C336.35 54.99, 349.93 38.93, 379.93 5.64 M326.53 73.17 C341.29 57.14, 355.14 36.64, 382.29 9.02 M326.53 73.17 C347.2 48.97, 367.48 26.48, 382.29 9.02 M332.17 72.77 C348.91 51.78, 366.05 34.35, 384.66 12.4 M332.17 72.77 C345.89 56.87, 360.02 41.97, 384.66 12.4 M337.16 73.13 C350.96 56.83, 362.64 44.64, 384.39 18.8 M337.16 73.13 C350.38 58.41, 363.8 42.97, 384.39 18.8 M342.8 72.74 C358.9 55.85, 372.92 38.47, 384.13 25.19 M342.8 72.74 C359.81 54.2, 374.02 35.6, 384.13 25.19 M347.79 73.1 C354.66 65.33, 364.11 55.68, 384.53 30.84 M347.79 73.1 C358.88 60.26, 371.55 46.08, 384.53 30.84 M352.77 73.46 C358.94 66.21, 369.89 55.52, 384.27 37.23 M352.77 73.46 C360.31 65.04, 369.69 54.26, 384.27 37.23 M358.42 73.07 C364.98 65.59, 369.02 58.96, 384 43.63 M358.42 73.07 C368.68 61.66, 377.08 51.51, 384 43.63 M363.4 73.43 C367.59 70.26, 373.45 61.3, 384.4 49.27 M363.4 73.43 C367.97 69.41, 372.38 63.07, 384.4 49.27 M367.74 74.54 C374.28 66.78, 379.58 61.94, 384.14 55.67 M367.74 74.54 C374.31 67.19, 380 58.66, 384.14 55.67 M374.69 72.64 C376.19 69.99, 382.01 66.54, 385.19 60.56 M374.69 72.64 C377.37 68.95, 380.91 65.58, 385.19 60.56" stroke="#ffec99" stroke-width="0.5" fill="none"></path><path d="M18.25 0 M18.25 0 C129.54 2.72, 242.28 1.14, 365.75 0 M18.25 0 C127.66 -1.41, 238.58 -1.21, 365.75 0 M365.75 0 C378.27 0.9, 382.95 5.36, 384 18.25 M365.75 0 C380.07 0.47, 383.93 4.97, 384 18.25 M384 18.25 C384.02 33.84, 384.87 48.45, 384 54.75 M384 18.25 C384.01 28.7, 384.22 39.53, 384 54.75 M384 54.75 C382.32 67.61, 378.7 73.98, 365.75 73 M384 54.75 C385.93 68.19, 378.42 74.36, 365.75 73 M365.75 73 C288.24 70.59, 213.1 72.01, 18.25 73 M365.75 73 C265.08 74.84, 163.23 75.38, 18.25 73 M18.25 73 C7.8 72.9, 1.58 68.54, 0 54.75 M18.25 73 C7.06 72.31, -0.46 65.42, 0 54.75 M0 54.75 C0.31 45.73, -1.55 34.03, 0 18.25 M0 54.75 C0.72 40.09, -0.05 27.49, 0 18.25 M0 18.25 C1.55 7.28, 6.06 -1.74, 18.25 0 M0 18.25 C1.91 4.6, 5.29 1.6, 18.25 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(297.58984375 586.578125) rotate(0 70.3125 28.80000000000001)"><text x="0" y="0" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">directories: []</text><text x="0" y="19.2" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">files: []</text><text x="0" y="38.4" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">symlinks: []</text></g><g stroke-linecap="round" transform="translate(297.01171875 700.19140625) rotate(0 192 63)"><path d="M4.68 12.02 C4.68 12.02, 4.68 12.02, 4.68 12.02 M4.68 12.02 C4.68 12.02, 4.68 12.02, 4.68 12.02 M1.14 22.19 C5.68 16.12, 10.34 11.35, 18.19 2.57 M1.14 22.19 C5.63 15.12, 11.63 10.54, 18.19 2.57 M2.84 26.32 C9.4 19.34, 14.38 14.47, 24.49 1.42 M2.84 26.32 C7.02 21.88, 12.43 16.24, 24.49 1.42 M3.24 31.97 C12.37 22.32, 23.31 9.97, 30.14 1.02 M3.24 31.97 C8.75 26.69, 14.07 19.43, 30.14 1.02 M2.98 38.36 C11.61 29.09, 22.34 16.73, 34.47 2.14 M2.98 38.36 C14.61 26.7, 24.62 13.57, 34.47 2.14 M2.72 44.76 C8.44 35.48, 20.15 28.58, 39.46 2.5 M2.72 44.76 C9.81 35.38, 16.68 27.81, 39.46 2.5 M3.11 50.4 C17.54 33.89, 29.94 20.31, 45.1 2.1 M3.11 50.4 C20.74 31.63, 36.06 13.37, 45.1 2.1 M2.85 56.8 C18.58 35.97, 39.38 14.61, 50.09 2.46 M2.85 56.8 C15.02 42.55, 25.62 29.03, 50.09 2.46 M3.24 62.45 C13.28 49.17, 26.13 38.7, 55.73 2.07 M3.24 62.45 C14.41 50.21, 23.72 38.66, 55.73 2.07 M2.98 68.84 C13.36 56.63, 24.49 40.18, 60.72 2.43 M2.98 68.84 C25.38 43.36, 46.78 18.82, 60.72 2.43 M2.72 75.24 C27.68 45.78, 49.87 21.46, 66.36 2.03 M2.72 75.24 C27.36 48.58, 50.99 21.41, 66.36 2.03 M3.12 80.88 C26.98 53.98, 51.79 26.74, 71.35 2.39 M3.12 80.88 C24.82 57.11, 46.21 30.7, 71.35 2.39 M2.85 87.28 C25.83 58.82, 49.6 32.81, 76.33 2.75 M2.85 87.28 C18.86 69.84, 32.85 53.27, 76.33 2.75 M3.25 92.92 C31.02 64.11, 55.21 32.05, 81.98 2.36 M3.25 92.92 C29.09 64.51, 54.01 35.08, 81.98 2.36 M0.36 102.34 C18.06 82.88, 34.35 63.19, 86.96 2.72 M0.36 102.34 C22.5 76.81, 45.99 50.02, 86.96 2.72 M2.07 106.47 C35.94 64.04, 71.16 24.59, 92.61 2.33 M2.07 106.47 C29.3 74.98, 57.21 42, 92.61 2.33 M3.78 110.61 C32.64 72.16, 65.08 38.52, 97.59 2.69 M3.78 110.61 C27.71 83.59, 50.59 56.73, 97.59 2.69 M5.48 114.74 C31.81 84.38, 61.24 51.3, 103.24 2.29 M5.48 114.74 C38.37 76.78, 69.65 40.61, 103.24 2.29 M7.85 118.12 C37.24 84.78, 67.86 51.75, 108.22 2.65 M7.85 118.12 C40.51 79.72, 74.23 40.65, 108.22 2.65 M10.86 120.75 C33.15 93.28, 59.27 65.67, 113.87 2.26 M10.86 120.75 C41.44 83.49, 74.67 47.06, 113.87 2.26 M14.54 122.62 C54.9 77.33, 93.45 31.72, 118.85 2.62 M14.54 122.62 C36.96 96.13, 58.59 69.9, 118.85 2.62 M18.21 124.48 C60.03 78.24, 98.36 27.73, 124.5 2.22 M18.21 124.48 C47.99 89.82, 78.58 52.73, 124.5 2.22 M23.2 124.84 C60.93 81.34, 96.9 37.41, 129.48 2.58 M23.2 124.84 C51.15 90.76, 80.7 57.55, 129.48 2.58 M27.53 125.96 C67.64 79.48, 108.95 32.16, 135.13 2.19 M27.53 125.96 C50.35 100, 70.45 76.16, 135.13 2.19 M32.52 126.32 C67.96 82.8, 105.29 42.95, 140.11 2.55 M32.52 126.32 C55.09 102.84, 75.65 76.61, 140.11 2.55 M37.51 126.68 C76.58 83.49, 111.39 39.46, 145.76 2.15 M37.51 126.68 C68.68 90.35, 99.78 54.4, 145.76 2.15 M43.15 126.29 C79.05 86.81, 113.32 46.01, 150.74 2.51 M43.15 126.29 C83.16 78.74, 122.47 32.63, 150.74 2.51 M48.14 126.65 C84.95 86.46, 121.01 43.96, 156.39 2.12 M48.14 126.65 C77.47 92.07, 106.15 57.86, 156.39 2.12 M53.12 127.01 C94.06 83.46, 132.13 35.83, 161.37 2.48 M53.12 127.01 C83.35 90.25, 115.88 54.9, 161.37 2.48 M58.77 126.61 C88.9 91.94, 120.62 55.5, 167.02 2.08 M58.77 126.61 C80.71 101.71, 103.08 74.94, 167.02 2.08 M63.75 126.97 C94.83 94.26, 121.35 59.65, 172 2.44 M63.75 126.97 C99.19 87.16, 134.08 46.33, 172 2.44 M69.4 126.58 C96.06 98.29, 123.08 66.58, 177.65 2.05 M69.4 126.58 C94.05 98.86, 118.43 72.82, 177.65 2.05 M74.38 126.94 C110.22 87.09, 146.32 44.12, 182.63 2.41 M74.38 126.94 C112.7 80.36, 152.52 34.24, 182.63 2.41 M80.03 126.54 C112.63 85.84, 146.64 47.21, 188.28 2.01 M80.03 126.54 C118.52 83.47, 154.62 39.47, 188.28 2.01 M85.01 126.9 C123.66 84.49, 160.64 41.03, 193.26 2.37 M85.01 126.9 C107.24 101.3, 128.84 75.24, 193.26 2.37 M90.66 126.51 C128.32 79.98, 168.35 34.11, 198.25 2.73 M90.66 126.51 C125.23 87.51, 159.47 48.47, 198.25 2.73 M95.64 126.87 C136.32 80.89, 176.13 32.73, 203.89 2.34 M95.64 126.87 C130.67 85.7, 167.03 45.03, 203.89 2.34 M101.29 126.47 C138.72 85.68, 174.7 42.24, 208.88 2.7 M101.29 126.47 C141.27 81.26, 181.48 32.8, 208.88 2.7 M106.27 126.83 C133.13 96.99, 159.87 69.21, 214.52 2.31 M106.27 126.83 C139.22 87.92, 173.99 50.04, 214.52 2.31 M111.92 126.44 C139.17 92.95, 170.03 64.03, 219.51 2.67 M111.92 126.44 C140.35 94.34, 169.24 63.52, 219.51 2.67 M116.9 126.8 C139.41 102.01, 161.49 74.65, 225.15 2.27 M116.9 126.8 C143.96 97.47, 169.6 65.58, 225.15 2.27 M122.55 126.4 C151.81 92.7, 184.46 60.16, 230.14 2.63 M122.55 126.4 C144.55 101.98, 167.71 77.15, 230.14 2.63 M127.53 126.76 C155.24 93.9, 184.87 59.39, 235.78 2.24 M127.53 126.76 C162.55 85.69, 197.14 44.32, 235.78 2.24 M133.18 126.37 C156.24 97.59, 181.25 74.22, 240.77 2.6 M133.18 126.37 C169.91 83.87, 205.04 43.06, 240.77 2.6 M138.16 126.73 C171.62 83.67, 210.01 43.26, 246.41 2.2 M138.16 126.73 C162.75 98.55, 185.88 71.86, 246.41 2.2 M143.81 126.33 C167.53 96.38, 195.44 68.77, 251.4 2.56 M143.81 126.33 C175.38 87.65, 208.52 52.45, 251.4 2.56 M148.79 126.69 C186.15 81.29, 226.32 36.57, 257.04 2.17 M148.79 126.69 C185.38 86.13, 221.92 44.72, 257.04 2.17 M154.44 126.3 C190.78 83.15, 229.34 36.03, 262.03 2.53 M154.44 126.3 C178.51 97.01, 203.83 70.69, 262.03 2.53 M159.42 126.66 C189.83 93.77, 217.8 63.16, 267.67 2.13 M159.42 126.66 C196.22 84.11, 234.53 42.17, 267.67 2.13 M165.07 126.27 C199.58 88.17, 233.46 45.58, 272.66 2.49 M165.07 126.27 C204.81 83.18, 243.36 38.31, 272.66 2.49 M170.05 126.63 C191.4 102.17, 214.65 75.55, 278.3 2.1 M170.05 126.63 C194.79 98.87, 219.21 68.85, 278.3 2.1 M175.04 126.99 C218.62 76.89, 259.45 31.99, 283.29 2.46 M175.04 126.99 C200.78 100.7, 225.33 71.6, 283.29 2.46 M180.68 126.59 C214.02 88.6, 246.67 51.74, 288.93 2.06 M180.68 126.59 C212.29 91.19, 241.62 56.43, 288.93 2.06 M185.67 126.95 C215.71 95.07, 243.49 60.99, 293.92 2.42 M185.67 126.95 C216.01 94.84, 243.45 62.69, 293.92 2.42 M191.31 126.56 C229.63 85.76, 263.97 41.71, 299.56 2.03 M191.31 126.56 C222.59 88.19, 255.96 51.19, 299.56 2.03 M196.3 126.92 C217.19 100.03, 243 74.87, 304.55 2.39 M196.3 126.92 C218.88 99.48, 243.28 72.58, 304.55 2.39 M201.94 126.52 C245.64 78.21, 284.33 30.24, 309.54 2.75 M201.94 126.52 C240.53 84.15, 277.64 41.48, 309.54 2.75 M206.93 126.88 C248.75 80.42, 287.71 35.97, 315.18 2.36 M206.93 126.88 C240.59 87.82, 273.05 49.59, 315.18 2.36 M212.57 126.49 C242.06 93.98, 270.55 61.9, 320.17 2.72 M212.57 126.49 C238.53 99.42, 261.94 72.17, 320.17 2.72 M217.56 126.85 C255.21 80.1, 296.4 36.1, 325.81 2.32 M217.56 126.85 C248.46 91.27, 280.97 56.5, 325.81 2.32 M223.2 126.45 C245.32 96.89, 272.92 68.02, 330.8 2.68 M223.2 126.45 C246.93 99.22, 269.26 73.84, 330.8 2.68 M228.19 126.81 C266.27 81.56, 302.8 39.44, 336.44 2.29 M228.19 126.81 C268.68 80.87, 310.31 31.49, 336.44 2.29 M233.83 126.42 C261.99 92.9, 293.86 58.89, 341.43 2.65 M233.83 126.42 C265.72 90.09, 297.62 53.24, 341.43 2.65 M238.82 126.78 C276.56 81.01, 314.76 40.31, 347.07 2.25 M238.82 126.78 C275.07 83.85, 309.78 43.16, 347.07 2.25 M244.47 126.38 C281.08 86.31, 311.83 47.08, 353.37 1.1 M244.47 126.38 C284.42 79.98, 324.2 34.41, 353.37 1.1 M249.45 126.74 C283.83 89.61, 316.66 48.25, 358.36 1.46 M249.45 126.74 C288.06 81.49, 329.09 35.62, 358.36 1.46 M255.1 126.35 C286.95 88.5, 321.78 49.74, 362.69 2.58 M255.1 126.35 C278.54 101.51, 299.57 76.6, 362.69 2.58 M260.08 126.71 C289.9 92.16, 318.76 59.09, 367.68 2.94 M260.08 126.71 C286.6 97.68, 309.24 69.16, 367.68 2.94 M265.73 126.32 C304.67 81.34, 343.89 36.13, 371.35 4.81 M265.73 126.32 C296.05 89.95, 328.32 54.46, 371.35 4.81 M270.71 126.68 C312.11 79.85, 350.1 38.08, 375.03 6.68 M270.71 126.68 C308.29 85.27, 344.32 43.1, 375.03 6.68 M276.36 126.28 C302.37 95.05, 332.55 61.6, 377.39 10.06 M276.36 126.28 C300.17 99.46, 322.39 72.19, 377.39 10.06 M281.34 126.64 C304.76 96.82, 332.3 67.75, 380.41 12.68 M281.34 126.64 C307.49 95.18, 332.91 65.5, 380.41 12.68 M286.33 127 C314.42 93.01, 345.57 57.56, 381.46 17.57 M286.33 127 C313.51 94.85, 341.77 62.68, 381.46 17.57 M291.97 126.61 C313.48 100.45, 339.17 75.97, 383.16 21.7 M291.97 126.61 C311.58 103.09, 333.11 79.56, 383.16 21.7 M296.96 126.97 C320.59 100.34, 342.62 72.36, 384.87 25.84 M296.96 126.97 C321.66 99.71, 344.27 72.56, 384.87 25.84 M302.6 126.57 C334.11 86.35, 368.78 51.97, 385.27 31.48 M302.6 126.57 C330.75 94.29, 357.85 62.13, 385.27 31.48 M307.59 126.93 C326.81 105.47, 343.03 83.78, 385 37.88 M307.59 126.93 C327.54 105.51, 344.6 84.21, 385 37.88 M313.23 126.54 C332.05 106.16, 349.05 86.64, 385.4 43.52 M313.23 126.54 C332.89 103.96, 352.85 83.32, 385.4 43.52 M318.22 126.9 C331.98 110.58, 346.57 93.14, 385.14 49.92 M318.22 126.9 C331.03 111.1, 344.19 94.95, 385.14 49.92 M323.86 126.5 C335.72 108.89, 350.56 93.22, 385.53 55.56 M323.86 126.5 C345.11 101.47, 364.04 79.57, 385.53 55.56 M328.85 126.86 C346.1 108.02, 361.21 90.1, 385.27 61.96 M328.85 126.86 C342.2 112.06, 355.94 96.74, 385.27 61.96 M334.49 126.47 C350.84 109.97, 364.71 94.6, 385.67 67.6 M334.49 126.47 C353.49 104.81, 372.17 82.14, 385.67 67.6 M339.48 126.83 C348.96 114.71, 363.17 101.01, 385.4 74 M339.48 126.83 C354.75 110.09, 368.15 93.09, 385.4 74 M345.12 126.43 C354.84 117.22, 365.07 103.77, 385.8 79.64 M345.12 126.43 C358.46 110.23, 371.31 95.91, 385.8 79.64 M350.11 126.79 C364.3 111.08, 375.22 97.18, 385.54 86.04 M350.11 126.79 C363.14 112.28, 374.94 98.31, 385.54 86.04 M354.44 127.91 C362.81 117.31, 370.87 110.18, 385.28 92.44 M354.44 127.91 C362.9 118.85, 369.83 111.91, 385.28 92.44 M360.74 126.76 C367.63 119.85, 374.61 113.88, 386.98 96.57 M360.74 126.76 C369 116.72, 377.31 108.85, 386.98 96.57 M367.7 124.86 C371.04 118.25, 376.15 112.61, 382.13 108.25 M367.7 124.86 C371.11 120.48, 374.22 117.39, 382.13 108.25" stroke="#ffec99" stroke-width="0.5" fill="none"></path><path d="M31.5 0 M31.5 0 C144.73 2.16, 257.89 1.55, 352.5 0 M31.5 0 C123.21 -0.03, 215.81 -0.04, 352.5 0 M352.5 0 C373.48 -1.54, 384.54 11.54, 384 31.5 M352.5 0 C371.52 1.89, 382.06 11.18, 384 31.5 M384 31.5 C384.2 57.65, 382.83 81.45, 384 94.5 M384 31.5 C384.94 50.55, 383.94 67.7, 384 94.5 M384 94.5 C385.07 116.86, 374.35 127.03, 352.5 126 M384 94.5 C385.17 117.04, 373.57 125.11, 352.5 126 M352.5 126 C247.11 128.47, 140.42 127.09, 31.5 126 M352.5 126 C271.97 126.76, 193.29 127.03, 31.5 126 M31.5 126 C11.34 124.76, -1.26 114.07, 0 94.5 M31.5 126 C9.77 126.57, -1.64 113.31, 0 94.5 M0 94.5 C0.36 74.76, 1.16 53.5, 0 31.5 M0 94.5 C-0.19 81.51, -0.09 68.38, 0 31.5 M0 31.5 C-0.91 8.69, 10.9 0.79, 31.5 0 M0 31.5 C2.16 12.64, 11.39 -0.47, 31.5 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(302.01171875 705.19140625) rotate(0 145.3125 57.60000000000002)"><text x="0" y="0" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">directories:</text><text x="0" y="19.2" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge"> - name: a</text><text x="0" y="38.4" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">   digest: &lt;directory-a-digest&gt;</text><text x="0" y="57.599999999999994" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">   size: 0</text><text x="0" y="76.8" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">files: []</text><text x="0" y="96" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">symlinks: []</text></g><g transform="translate(303.75390625 45.62890625) rotate(0 89.0625 9.599999999999994)"><text x="0" y="0" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#f08c00" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">DIRECTORY_WITH_KEEP</text></g><g transform="translate(308.25390625 240.4765625) rotate(0 98.4375 9.599999999999994)"><text x="0" y="0" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#f08c00" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">DIRECTORY_COMPLICATED</text></g><g transform="translate(307.28125 554.6148437500001) rotate(0 51.5625 9.600000000000023)"><text x="0" y="0" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#f08c00" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">DIRECTORY_A</text></g><g transform="translate(310.6875 674.4585937500001) rotate(0 51.5625 9.600000000000023)"><text x="0" y="0" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#f08c00" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">DIRECTORY_B</text></g><g transform="translate(18.95703125 42.53671874999998) rotate(0 28.125 9.599999999999994)"><text x="0" y="0" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#1971c2" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">BLOB_A</text></g><g transform="translate(22.55078125 130.24374999999998) rotate(0 28.125 9.599999999999994)"><text x="0" y="0" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#1971c2" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">BLOB_B</text></g><g transform="translate(13.5546875 210.27109374999998) rotate(0 46.875 9.599999999999994)"><text x="0" y="0" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#1971c2" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">BLOB_EMPTY</text></g><g stroke-linecap="round"><g transform="translate(388.15234375 147.38671875) rotate(0 -178.16880695513453 57.143675736445005)"><path d="M-0.52 1.13 C-60.08 19.9, -297.46 94.83, -357.2 113.61 M1.4 0.68 C-58.25 18.98, -298.08 93.09, -357.74 111.59" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g transform="translate(388.15234375 147.38671875) rotate(0 -178.16880695513453 57.143675736445005)"><path d="M-331.96 94.73 C-340.78 97.36, -347.89 102.93, -357.37 112.7 M-333.95 93.43 C-339.75 97.47, -345.42 102.99, -358.69 111" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g transform="translate(388.15234375 147.38671875) rotate(0 -178.16880695513453 57.143675736445005)"><path d="M-325.89 114.33 C-336.76 110.86, -345.74 110.35, -357.37 112.7 M-327.89 113.04 C-335.08 112.25, -342.26 112.89, -358.69 111" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g></g><mask></mask><g stroke-linecap="round"><g transform="translate(399.61021320513464 319.000855513555) rotate(0 132.18131301403048 -119.00356223583219)"><path d="M-0.42 1.04 C43.76 -38.95, 220.7 -199.19, 264.79 -239.05 M1.55 0.54 C45.63 -39.42, 220.36 -198.5, 263.94 -238.07" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g transform="translate(399.61021320513464 319.000855513555) rotate(0 132.18131301403048 -119.00356223583219)"><path d="M251.72 -211.19 C251.81 -219.53, 257.18 -225.36, 265.07 -236.65 M250.73 -212.1 C253.06 -218.02, 257.3 -223.31, 264.08 -238.4" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g transform="translate(399.61021320513464 319.000855513555) rotate(0 132.18131301403048 -119.00356223583219)"><path d="M237.91 -226.37 C241.88 -230.52, 251.02 -232.22, 265.07 -236.65 M236.92 -227.29 C242.47 -229.5, 250.03 -231.14, 264.08 -238.4" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g></g><mask></mask><g stroke-linecap="round"><g transform="translate(396.39146320513464 414.62194926355494) rotate(0 -181.26559592220468 -76.52005761642474)"><path d="M0.21 0.21 C-60.17 -24.98, -301.37 -126.03, -361.5 -151.72 M-1.14 -0.73 C-61.8 -26.18, -302.73 -127.84, -362.74 -153.25" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g transform="translate(396.39146320513464 414.62194926355494) rotate(0 -181.26559592220468 -76.52005761642474)"><path d="M-332.43 -152.64 C-341.83 -151.66, -356.48 -151.02, -362.88 -151.77 M-332.23 -151.12 C-344.25 -151.99, -356 -152.45, -362.93 -152.59" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g transform="translate(396.39146320513464 414.62194926355494) rotate(0 -181.26559592220468 -76.52005761642474)"><path d="M-340.42 -133.74 C-346.81 -139.68, -358.54 -145.95, -362.88 -151.77 M-340.22 -132.22 C-349.22 -140.26, -357.91 -147.96, -362.93 -152.59" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g></g><mask></mask><g stroke-linecap="round"><g transform="translate(407.44140625 751.55078125) rotate(0 45.37286624398547 -48.585232615470886)"><path d="M-0.84 0.01 C13.92 -16, 74.3 -80.87, 89.45 -97.18 M0.92 -1.04 C15.95 -16.73, 76.9 -79.62, 91.58 -95.58" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g transform="translate(407.44140625 751.55078125) rotate(0 45.37286624398547 -48.585232615470886)"><path d="M79.65 -69.91 C84.59 -79.16, 87.99 -87.88, 90.6 -97.23 M78.91 -67.92 C83.62 -76.08, 87.25 -85.83, 90.62 -96.45" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g transform="translate(407.44140625 751.55078125) rotate(0 45.37286624398547 -48.585232615470886)"><path d="M64.75 -84.03 C75.18 -87.85, 84.2 -91.25, 90.6 -97.23 M64.01 -82.04 C73.25 -85.89, 81.47 -91.29, 90.62 -96.45" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g></g><mask></mask><g transform="translate(796.08984375 10.884374999999977) rotate(0 37.5 9.599999999999994)"><text x="0" y="0" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">PathInfo</text></g><g stroke-linecap="round" transform="translate(800.94921875 70.3515625) rotate(0 238.5 66.5)"><path d="M7.86 8.16 C7.86 8.16, 7.86 8.16, 7.86 8.16 M7.86 8.16 C7.86 8.16, 7.86 8.16, 7.86 8.16 M3.66 19.09 C10.39 14.59, 12.36 7.63, 18.1 2.48 M3.66 19.09 C7.72 12.99, 12.69 8.45, 18.1 2.48 M2.75 26.24 C9.54 18.05, 20.15 8.22, 24.4 1.33 M2.75 26.24 C10.63 16.6, 19.07 7.71, 24.4 1.33 M2.48 32.64 C10.84 25.1, 18.62 16.99, 30.7 0.18 M2.48 32.64 C10.3 22, 19.4 12.2, 30.7 0.18 M2.88 38.28 C13.73 28.82, 21.58 17.53, 34.37 2.05 M2.88 38.28 C11.28 29.81, 18.49 19.2, 34.37 2.05 M2.62 44.68 C15.27 32.46, 26.63 18.87, 39.36 2.41 M2.62 44.68 C12.58 32.64, 23.98 18.88, 39.36 2.41 M2.36 51.07 C15.78 39.12, 25.44 23.33, 45 2.02 M2.36 51.07 C15.86 35.07, 30.03 18.81, 45 2.02 M2.75 56.72 C21.32 37.62, 39.51 16.97, 49.99 2.38 M2.75 56.72 C15.6 42.48, 27.62 27.63, 49.99 2.38 M2.49 63.11 C19.73 43.61, 35.49 24.47, 55.63 1.98 M2.49 63.11 C14.04 50.89, 25.8 36.73, 55.63 1.98 M2.88 68.76 C20.08 47.93, 38.2 25.58, 60.62 2.34 M2.88 68.76 C19.66 48.91, 37.22 28.6, 60.62 2.34 M2.62 75.16 C19.27 53.2, 38.94 35.17, 66.26 1.95 M2.62 75.16 C17.89 55.5, 34.42 36.25, 66.26 1.95 M2.36 81.55 C30.5 51, 58.03 17.88, 71.25 2.31 M2.36 81.55 C16.96 64.32, 31.61 47.37, 71.25 2.31 M2.76 87.2 C31.87 55.75, 56.06 22.7, 76.89 1.91 M2.76 87.2 C24.31 61.65, 46.29 35.63, 76.89 1.91 M2.49 93.59 C32.62 61.39, 61.22 28.82, 81.88 2.27 M2.49 93.59 C23.69 67.97, 45.72 43.7, 81.88 2.27 M2.89 99.24 C23.68 73.49, 45.29 48.44, 87.52 1.88 M2.89 99.24 C26.99 71.79, 50.36 44.89, 87.52 1.88 M2.63 105.64 C23.82 79.99, 47.73 56.76, 92.51 2.24 M2.63 105.64 C29.41 76.09, 53.71 47.33, 92.51 2.24 M3.02 111.28 C35.76 74.46, 69.37 34.13, 98.15 1.85 M3.02 111.28 C24.1 86.82, 46.96 62.48, 98.15 1.85 M4.73 115.41 C41.64 74.79, 76.39 31.84, 103.14 2.21 M4.73 115.41 C35.15 80.63, 65.61 44.22, 103.14 2.21 M5.12 121.06 C38.67 80.99, 74.17 42.93, 108.78 1.81 M5.12 121.06 C37.52 87.2, 67.81 51.07, 108.78 1.81 M8.14 123.68 C45.61 75.63, 85.4 30.08, 113.77 2.17 M8.14 123.68 C46.84 78.16, 85.53 34.79, 113.77 2.17 M10.51 127.06 C51.38 83, 90.78 34.09, 119.41 1.78 M10.51 127.06 C44.83 86.58, 79.34 47.77, 119.41 1.78 M13.52 129.68 C38.07 99.79, 58.32 73.05, 124.4 2.14 M13.52 129.68 C50.9 87.61, 88.06 45.68, 124.4 2.14 M17.2 131.55 C58.64 84.2, 95.67 41.29, 130.04 1.74 M17.2 131.55 C41.93 103.22, 66.7 72.66, 130.04 1.74 M20.87 133.42 C57.62 92.2, 92.65 51.53, 135.03 2.1 M20.87 133.42 C65.94 80.91, 110.67 28.91, 135.03 2.1 M25.86 133.78 C57.72 93.6, 90.77 57.22, 140.67 1.71 M25.86 133.78 C56.65 99.16, 86.75 62.82, 140.67 1.71 M30.19 134.9 C76.39 84.32, 123.04 30.78, 145.66 2.07 M30.19 134.9 C59.94 102.4, 88.51 68.91, 145.66 2.07 M35.18 135.26 C63.65 103.83, 88.19 71.92, 150.65 2.43 M35.18 135.26 C77.05 84.73, 121.83 35.2, 150.65 2.43 M40.17 135.62 C76.62 93.15, 111.75 54.6, 156.29 2.03 M40.17 135.62 C72.44 97.03, 106.71 60.31, 156.29 2.03 M45.81 135.22 C68.12 108.89, 93.36 81.85, 161.28 2.39 M45.81 135.22 C93.12 81.98, 137.9 28.67, 161.28 2.39 M50.8 135.58 C99.08 85.21, 141.4 29.85, 166.92 2 M50.8 135.58 C95.54 85.04, 137.27 35.33, 166.92 2 M56.44 135.19 C86.16 100.81, 118.44 64.39, 171.91 2.36 M56.44 135.19 C85.59 100.01, 117.34 65.71, 171.91 2.36 M61.43 135.55 C93.02 99.32, 126.23 60.67, 177.55 1.96 M61.43 135.55 C98.63 92.46, 135.75 49.47, 177.55 1.96 M66.41 135.91 C88.8 108.25, 112.86 81.48, 182.54 2.32 M66.41 135.91 C91.41 106.52, 117.33 75.61, 182.54 2.32 M72.06 135.51 C104.73 95.48, 142.51 55.2, 188.18 1.93 M72.06 135.51 C99.45 104.83, 124.74 75.89, 188.18 1.93 M77.04 135.87 C115.11 94.08, 147.51 55.24, 193.17 2.29 M77.04 135.87 C100.11 110.76, 122.58 83.57, 193.17 2.29 M82.69 135.48 C116.4 97.33, 152.41 55.98, 198.81 1.9 M82.69 135.48 C110.99 102.8, 139.68 69.49, 198.81 1.9 M87.67 135.84 C132 84.75, 175.61 37.52, 203.8 2.26 M87.67 135.84 C116.95 102.95, 145.57 69.87, 203.8 2.26 M93.32 135.44 C137.75 81.91, 184.41 30.9, 209.44 1.86 M93.32 135.44 C125.76 94, 160.68 54.19, 209.44 1.86 M98.3 135.8 C129.81 99.97, 161.28 60.26, 214.43 2.22 M98.3 135.8 C133.08 96.91, 166.85 57.98, 214.43 2.22 M103.95 135.41 C130.43 101.26, 160.91 69.64, 220.07 1.83 M103.95 135.41 C145.52 86.13, 188.77 38.68, 220.07 1.83 M108.93 135.77 C131.74 110.52, 156.29 82.4, 225.06 2.19 M108.93 135.77 C155.58 84.93, 200.54 32.47, 225.06 2.19 M114.58 135.38 C157.28 86.99, 201.96 37.19, 230.7 1.79 M114.58 135.38 C152.78 93.11, 190.04 49.2, 230.7 1.79 M119.56 135.74 C147.35 100.73, 175.19 69.8, 235.69 2.15 M119.56 135.74 C152.35 99.22, 182.76 63.91, 235.69 2.15 M125.21 135.34 C151.37 104.17, 174.15 75, 241.33 1.76 M125.21 135.34 C170.53 85.3, 211.95 36.33, 241.33 1.76 M130.19 135.7 C173.94 86.44, 220.64 33.18, 246.32 2.12 M130.19 135.7 C172.93 86.98, 216.51 38.35, 246.32 2.12 M135.84 135.31 C174.21 90.08, 208.21 49.75, 251.96 1.72 M135.84 135.31 C158.72 109.66, 182.85 82.09, 251.96 1.72 M140.82 135.67 C177.31 93.41, 212.97 53.24, 256.95 2.08 M140.82 135.67 C166.68 105.14, 194.13 75.79, 256.95 2.08 M146.47 135.27 C182.12 93.41, 217.19 51.75, 262.59 1.69 M146.47 135.27 C192.8 83.11, 239.07 30.19, 262.59 1.69 M151.45 135.63 C195.86 84.04, 242.39 29.83, 267.58 2.05 M151.45 135.63 C182.45 96.24, 215.98 59.61, 267.58 2.05 M157.1 135.24 C193.93 90.8, 230.49 48.73, 272.56 2.41 M157.1 135.24 C186.1 101.92, 213.74 67.58, 272.56 2.41 M162.08 135.6 C204.71 88.09, 242.57 43.67, 278.21 2.01 M162.08 135.6 C195.51 97.13, 227.49 59.04, 278.21 2.01 M167.73 135.2 C191.02 109.31, 213.95 82.1, 283.19 2.37 M167.73 135.2 C208.32 87.02, 247.72 39.69, 283.19 2.37 M172.71 135.56 C211.05 92.35, 249.27 45.71, 288.84 1.98 M172.71 135.56 C204.27 99.8, 232.12 66.33, 288.84 1.98 M178.36 135.17 C208.57 98.05, 240.33 61.56, 293.82 2.34 M178.36 135.17 C224.01 83.75, 267.9 32.95, 293.82 2.34 M183.34 135.53 C211.9 102.21, 241.28 69.57, 299.47 1.94 M183.34 135.53 C225.07 89.34, 264.88 43.51, 299.47 1.94 M188.33 135.89 C232.49 86.72, 275.85 35.32, 304.45 2.3 M188.33 135.89 C226.55 88.72, 267.12 42.68, 304.45 2.3 M193.97 135.49 C226.44 96.44, 258.36 62.18, 310.1 1.91 M193.97 135.49 C239.79 82.46, 285.44 30.27, 310.1 1.91 M198.96 135.85 C228.28 100.09, 257.95 68.9, 315.08 2.27 M198.96 135.85 C235.09 93.52, 272.84 52.67, 315.08 2.27 M204.6 135.46 C239.68 94.74, 275.29 55.5, 320.73 1.88 M204.6 135.46 C244.23 91.28, 283.02 46.4, 320.73 1.88 M209.59 135.82 C250.03 93.16, 286.49 47.86, 325.71 2.24 M209.59 135.82 C241.67 97.03, 276.6 58.48, 325.71 2.24 M215.23 135.42 C252.93 93.98, 290.73 49.25, 331.36 1.84 M215.23 135.42 C239.46 107.48, 265.75 77.82, 331.36 1.84 M220.22 135.78 C258.31 87.12, 298.41 41.67, 336.34 2.2 M220.22 135.78 C261.35 88.79, 302.28 41.23, 336.34 2.2 M225.86 135.39 C247.19 109.85, 271.45 83.37, 341.99 1.81 M225.86 135.39 C269.09 87.75, 311.09 39.46, 341.99 1.81 M230.85 135.75 C271.9 89.87, 311.65 42.41, 346.97 2.17 M230.85 135.75 C266.69 94.9, 300.46 56.72, 346.97 2.17 M236.49 135.36 C279.54 90.41, 318.34 43.37, 352.62 1.77 M236.49 135.36 C262.51 106.22, 289.8 75.87, 352.62 1.77 M241.48 135.72 C270.62 102.2, 297.59 68.43, 357.6 2.13 M241.48 135.72 C267.4 106.57, 291.68 77.77, 357.6 2.13 M247.12 135.32 C272.55 104.17, 301.27 75.42, 363.25 1.74 M247.12 135.32 C286.54 88.24, 325.79 43.89, 363.25 1.74 M252.11 135.68 C285.8 94.95, 321.96 52.06, 368.23 2.1 M252.11 135.68 C286.48 98.48, 319.42 59.27, 368.23 2.1 M257.75 135.29 C287.3 103.83, 313.13 73.67, 373.88 1.7 M257.75 135.29 C296.47 90.19, 336.96 43.61, 373.88 1.7 M262.74 135.65 C294.04 101.96, 321.68 67.42, 378.86 2.06 M262.74 135.65 C285.88 109.26, 307.99 80.92, 378.86 2.06 M268.38 135.25 C301.94 102.32, 329.68 63.48, 383.85 2.42 M268.38 135.25 C312.43 84.8, 355.81 34.99, 383.85 2.42 M273.37 135.61 C307.98 93.43, 343.82 50.19, 389.49 2.03 M273.37 135.61 C314.49 86.55, 355.98 39.87, 389.49 2.03 M279.01 135.22 C315.47 96.77, 347.58 56.5, 394.48 2.39 M279.01 135.22 C310.17 98.46, 342.48 62.75, 394.48 2.39 M284 135.58 C309.56 103.84, 338.63 75.13, 400.12 1.99 M284 135.58 C310.36 104.37, 338.59 71.89, 400.12 1.99 M289.64 135.18 C312.79 106.98, 337.43 80.83, 405.11 2.35 M289.64 135.18 C334.14 86.06, 378.85 34.51, 405.11 2.35 M294.63 135.54 C322.11 103.4, 351.85 69.91, 410.75 1.96 M294.63 135.54 C321.72 103.68, 349.75 71.49, 410.75 1.96 M299.62 135.9 C347.67 83.43, 392.82 28.41, 415.74 2.32 M299.62 135.9 C340.13 91.24, 379.75 47.11, 415.74 2.32 M305.26 135.51 C337.44 97.55, 370.85 61.54, 421.38 1.93 M305.26 135.51 C331.72 105.39, 358.21 73.88, 421.38 1.93 M310.25 135.87 C347.77 87.67, 388.48 42.7, 426.37 2.29 M310.25 135.87 C343.24 98.26, 374.33 62.37, 426.37 2.29 M315.89 135.47 C358.33 83.2, 405.58 35.13, 432.01 1.89 M315.89 135.47 C342.14 103.92, 367.01 74.44, 432.01 1.89 M320.88 135.83 C349.09 105.42, 377.17 73.43, 437 2.25 M320.88 135.83 C348.8 103.87, 377.25 71.06, 437 2.25 M326.52 135.44 C357.37 98.83, 392.96 61.44, 442.64 1.86 M326.52 135.44 C359.38 95.45, 395.35 54.96, 442.64 1.86 M331.51 135.8 C371.1 90.29, 414.52 41.02, 447.63 2.22 M331.51 135.8 C375.19 84.86, 419.5 34.87, 447.63 2.22 M337.15 135.41 C360.41 108.49, 389.11 78.17, 452.62 2.58 M337.15 135.41 C365.83 101.31, 393.5 68.62, 452.62 2.58 M342.14 135.77 C374.92 95.35, 408.22 55.28, 456.95 3.69 M342.14 135.77 C375.97 95.74, 410.85 54.51, 456.95 3.69 M347.78 135.37 C386.27 93.45, 423.41 49.67, 461.94 4.05 M347.78 135.37 C388.88 89.16, 428.69 44.23, 461.94 4.05 M352.77 135.73 C391.81 93.38, 429.55 47.99, 464.96 6.68 M352.77 135.73 C395.76 88.88, 437.51 38.86, 464.96 6.68 M358.41 135.34 C384.9 101.59, 410.76 73.18, 469.29 7.79 M358.41 135.34 C381.72 110.3, 403.42 84.85, 469.29 7.79 M363.4 135.7 C391.38 101.39, 419.01 68.91, 471.65 11.17 M363.4 135.7 C388 108.1, 412.92 77.82, 471.65 11.17 M369.04 135.3 C402.56 98.71, 434.28 59.98, 474.67 13.79 M369.04 135.3 C400.06 99.18, 430.91 65.19, 474.67 13.79 M374.03 135.66 C409.08 94.39, 445.61 50.9, 475.72 18.68 M374.03 135.66 C395.27 111.63, 415.98 87.61, 475.72 18.68 M379.67 135.27 C413.73 94.24, 448.47 58.31, 477.43 22.82 M379.67 135.27 C399.49 110.9, 420.37 87.43, 477.43 22.82 M384.66 135.63 C405.61 110.26, 426.94 85.23, 479.13 26.95 M384.66 135.63 C415.84 98.51, 447.61 62.33, 479.13 26.95 M390.3 135.23 C419.31 104.53, 444.38 75.12, 479.53 32.59 M390.3 135.23 C411.06 109.85, 433.81 86.46, 479.53 32.59 M395.29 135.59 C423.34 101.52, 451.07 71.77, 479.27 38.99 M395.29 135.59 C425.27 99.57, 456.57 63.69, 479.27 38.99 M400.93 135.2 C416.38 116.25, 434.8 96.63, 479 45.39 M400.93 135.2 C430.56 100.88, 460.84 68, 479 45.39 M405.92 135.56 C424.18 114.12, 445.92 87.99, 479.4 51.03 M405.92 135.56 C430.48 106.84, 457.04 77.45, 479.4 51.03 M410.91 135.92 C425.56 119.11, 439.25 103.89, 479.14 57.43 M410.91 135.92 C432.34 111.55, 454.95 85.96, 479.14 57.43 M416.55 135.52 C437.03 112.31, 458.69 88.33, 479.53 63.07 M416.55 135.52 C441.28 108.64, 464.07 81.07, 479.53 63.07 M421.54 135.88 C439.7 114.89, 456.47 92.93, 479.27 69.47 M421.54 135.88 C433.45 119.98, 448.34 104.83, 479.27 69.47 M427.18 135.49 C444.19 117.51, 459.72 100.42, 479.66 75.11 M427.18 135.49 C444.66 114.13, 462.3 93.68, 479.66 75.11 M432.17 135.85 C446.83 119.86, 461.29 103.69, 479.4 81.51 M432.17 135.85 C442.13 123.55, 452.74 111.76, 479.4 81.51 M437.81 135.46 C448.92 124.72, 457.92 112.62, 479.8 87.15 M437.81 135.46 C453.55 117.76, 467.68 101.47, 479.8 87.15 M445.42 132.8 C459.39 120.23, 469.27 105.41, 479.54 93.55 M445.42 132.8 C454.06 123.31, 462.94 111.17, 479.54 93.55 M451.06 132.4 C463.15 119.27, 472.64 110.16, 477.96 101.46 M451.06 132.4 C458.03 124.07, 464.8 115.65, 477.96 101.46 M457.36 131.25 C462.65 126.55, 467.85 119.82, 477.05 108.61 M457.36 131.25 C462.56 125.74, 468.27 120.23, 477.05 108.61 M464.97 128.59 C469.23 123.21, 474.5 119.94, 476.13 115.76 M464.97 128.59 C466.4 126.62, 470.09 122.46, 476.13 115.76" stroke="#b2f2bb" stroke-width="0.5" fill="none"></path><path d="M32 0 M32 0 C134.76 -0.82, 237.15 -2.84, 445 0 M32 0 C117.14 0.77, 203.3 0.82, 445 0 M445 0 C464.46 1.31, 477.82 10, 477 32 M445 0 C468.12 -1.15, 476.89 8.73, 477 32 M477 32 C477.29 57.75, 476.77 81.02, 477 101 M477 32 C477.02 52.99, 476.55 76.44, 477 101 M477 101 C476.76 123.18, 464.89 132.41, 445 133 M477 101 C479.01 122.87, 468.12 132.27, 445 133 M445 133 C321.26 133.45, 195.94 134.33, 32 133 M445 133 C294.67 133.48, 144.05 133.68, 32 133 M32 133 C9.9 132.01, -1.58 121.23, 0 101 M32 133 C9.79 134.41, -1.71 120.88, 0 101 M0 101 C-1.18 83.33, -0.4 65.06, 0 32 M0 101 C0 80.69, 0.41 59.87, 0 32 M0 32 C1.82 10.89, 11.97 0.59, 32 0 M0 32 C-1.7 8.91, 9.45 -0.01, 32 0" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g transform="translate(805.94921875 75.3515625) rotate(0 220.3125 57.599999999999994)"><text x="0" y="0" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">node:</text><text x="0" y="19.2" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">  symlink:</text><text x="0" y="38.4" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">    name: 00000000000000000000000000000000-test</text><text x="0" y="57.599999999999994" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">    target: /foo</text><text x="0" y="76.8" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">references: []</text><text x="0" y="96" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">narinfo: โ€ฆ</text></g><g stroke-linecap="round" transform="translate(806.46875 225.2578125) rotate(0 238.5 72.5)"><path d="M7.86 8.16 C7.86 8.16, 7.86 8.16, 7.86 8.16 M7.86 8.16 C7.86 8.16, 7.86 8.16, 7.86 8.16 M3.66 19.09 C8.68 15.22, 9.53 10.88, 18.1 2.48 M3.66 19.09 C8.81 14.12, 11.51 9.97, 18.1 2.48 M2.75 26.24 C6.81 18.96, 11.51 16.19, 24.4 1.33 M2.75 26.24 C8.1 20.93, 12.69 14.7, 24.4 1.33 M2.48 32.64 C12.09 22.75, 21.05 8.83, 30.7 0.18 M2.48 32.64 C12.24 21.36, 23.11 8.33, 30.7 0.18 M2.88 38.28 C14.38 23.94, 25.95 11.59, 34.37 2.05 M2.88 38.28 C9.83 28.87, 18.57 19.97, 34.37 2.05 M2.62 44.68 C11.2 34.98, 16.8 26.09, 39.36 2.41 M2.62 44.68 C9.89 35.67, 19.19 25.97, 39.36 2.41 M2.36 51.07 C16.6 33.87, 30.06 19.6, 45 2.02 M2.36 51.07 C14.11 36.43, 26.4 24.25, 45 2.02 M2.75 56.72 C11.38 44.5, 23.52 31.72, 49.99 2.38 M2.75 56.72 C12.11 44.16, 21.93 34.17, 49.99 2.38 M2.49 63.11 C22.69 39.21, 44.13 13.8, 55.63 1.98 M2.49 63.11 C24.61 38.46, 45.21 14.7, 55.63 1.98 M2.88 68.76 C22.62 46.66, 38.87 25.27, 60.62 2.34 M2.88 68.76 C15.25 52.49, 29.63 37.02, 60.62 2.34 M2.62 75.16 C19.95 55.49, 39.17 32.32, 66.26 1.95 M2.62 75.16 C24.54 50.58, 44.69 26.49, 66.26 1.95 M2.36 81.55 C27.08 53.26, 56.1 20.67, 71.25 2.31 M2.36 81.55 C22.38 59.12, 42.38 37.91, 71.25 2.31 M2.76 87.2 C16.85 69.7, 34.99 53.54, 76.89 1.91 M2.76 87.2 C24.69 60.73, 48.08 33.35, 76.89 1.91 M2.49 93.59 C26.83 67.24, 48.57 42.72, 81.88 2.27 M2.49 93.59 C22.01 71.75, 40.83 49.63, 81.88 2.27 M2.89 99.24 C21.26 75.44, 42.16 55.2, 87.52 1.88 M2.89 99.24 C27.49 71.35, 50.92 41.12, 87.52 1.88 M2.63 105.64 C30.05 76.71, 56.77 44.63, 92.51 2.24 M2.63 105.64 C29.93 73.84, 55.66 42.59, 92.51 2.24 M2.37 112.03 C31.19 75.8, 63.69 43.11, 98.15 1.85 M2.37 112.03 C33.09 77.93, 61.62 44.45, 98.15 1.85 M2.76 117.68 C39.28 75.49, 75.08 36.84, 103.14 2.21 M2.76 117.68 C38.87 78.04, 74.17 36.06, 103.14 2.21 M3.16 123.32 C44.92 78.9, 86.85 29.88, 108.78 1.81 M3.16 123.32 C33.5 86.75, 64.72 49.98, 108.78 1.81 M4.86 127.45 C37.1 91.22, 67.62 56.08, 113.77 2.17 M4.86 127.45 C48.91 78.36, 90.69 30.13, 113.77 2.17 M5.26 133.1 C28.92 105.27, 51.55 78.45, 119.41 1.78 M5.26 133.1 C39.84 93.11, 73.53 54.29, 119.41 1.78 M7.62 136.47 C35.56 105.24, 62.69 73.44, 124.4 2.14 M7.62 136.47 C41.94 98.75, 76.48 57.38, 124.4 2.14 M10.64 139.1 C45.2 102.3, 79.7 60.38, 130.04 1.74 M10.64 139.1 C39.87 104.89, 70.76 71.45, 130.04 1.74 M14.31 140.97 C43.51 110.97, 68.26 78.62, 135.03 2.1 M14.31 140.97 C45.94 106.35, 77.72 71.79, 135.03 2.1 M17.33 143.59 C63.36 91.09, 109.79 35.17, 140.67 1.71 M17.33 143.59 C67.02 87.83, 115.79 32.77, 140.67 1.71 M21.01 145.46 C69.52 85.94, 122.53 32.68, 145.66 2.07 M21.01 145.46 C65.52 93.39, 110.85 41.72, 145.66 2.07 M25.34 146.58 C73.82 90.55, 124.58 31.62, 150.65 2.43 M25.34 146.58 C57.27 108.71, 90.6 71.59, 150.65 2.43 M32.29 144.67 C82.37 88.85, 130.86 32.77, 156.29 2.03 M32.29 144.67 C58.07 117.01, 83.71 85.72, 156.29 2.03 M37.28 145.03 C76.55 100.57, 116.26 57.79, 161.28 2.39 M37.28 145.03 C75.27 101.67, 112.08 57.69, 161.28 2.39 M42.92 144.64 C87.59 96.54, 128.11 43.6, 166.92 2 M42.92 144.64 C76.77 106.18, 112.16 66.71, 166.92 2 M47.91 145 C74.27 114.89, 99.04 86.08, 171.91 2.36 M47.91 145 C80.84 110.75, 111.51 74.33, 171.91 2.36 M52.9 145.36 C91.05 102.58, 127.57 62.7, 177.55 1.96 M52.9 145.36 C99.51 92.64, 144.44 40.01, 177.55 1.96 M58.54 144.96 C96.62 103.36, 132.1 61.4, 182.54 2.32 M58.54 144.96 C102.3 95.31, 145 44.66, 182.54 2.32 M63.53 145.32 C109.82 92.88, 153.81 37.96, 188.18 1.93 M63.53 145.32 C106.42 95.64, 150.95 44.77, 188.18 1.93 M69.17 144.93 C96.18 113.75, 124.05 81.34, 193.17 2.29 M69.17 144.93 C108.27 96.79, 149.34 50.87, 193.17 2.29 M74.16 145.29 C97.88 116.26, 126.66 87.16, 198.81 1.9 M74.16 145.29 C103.75 113.96, 133.25 80.61, 198.81 1.9 M79.8 144.9 C128.87 85.82, 177.7 31.59, 203.8 2.26 M79.8 144.9 C108.17 113.17, 135.25 78.83, 203.8 2.26 M84.79 145.26 C128.13 96.77, 168.82 46.48, 209.44 1.86 M84.79 145.26 C120.57 103.55, 156.93 61.55, 209.44 1.86 M90.43 144.86 C131.03 103.87, 164.62 58.8, 214.43 2.22 M90.43 144.86 C139.02 86.88, 188.33 30.13, 214.43 2.22 M95.42 145.22 C122.03 112.94, 150.45 84.35, 220.07 1.83 M95.42 145.22 C135.79 98.82, 175.14 53.71, 220.07 1.83 M101.06 144.83 C140.89 97.19, 181.91 54.17, 225.06 2.19 M101.06 144.83 C134.98 105.95, 167.66 68.29, 225.06 2.19 M106.05 145.19 C152.49 92.82, 198.26 40.96, 230.7 1.79 M106.05 145.19 C132.37 114.56, 159.47 83.34, 230.7 1.79 M111.69 144.79 C153.52 93.02, 197.5 42.26, 235.69 2.15 M111.69 144.79 C159.86 91.23, 206.41 36.81, 235.69 2.15 M116.68 145.15 C142.1 113.42, 167.54 83.21, 241.33 1.76 M116.68 145.15 C161.14 92.66, 207.79 41.99, 241.33 1.76 M122.32 144.76 C166.13 93.13, 209.82 39.8, 246.32 2.12 M122.32 144.76 C166.64 93.28, 213.29 39.18, 246.32 2.12 M127.31 145.12 C165.51 100.3, 206.72 56.81, 251.96 1.72 M127.31 145.12 C156.34 112.26, 187.1 77.6, 251.96 1.72 M132.95 144.72 C166.91 109.34, 195.98 71.92, 256.95 2.08 M132.95 144.72 C180.38 89.53, 229.12 34.54, 256.95 2.08 M137.94 145.08 C175.38 101.81, 210.1 63.31, 262.59 1.69 M137.94 145.08 C169.66 108.24, 200.14 72.75, 262.59 1.69 M143.58 144.69 C168.18 114.88, 194.29 86.3, 267.58 2.05 M143.58 144.69 C176.83 108.94, 208.19 70.29, 267.58 2.05 M148.57 145.05 C182.56 103.55, 216.29 66.03, 272.56 2.41 M148.57 145.05 C181.02 105.11, 215.28 67.56, 272.56 2.41 M154.21 144.65 C189.78 103.53, 226.3 57.46, 278.21 2.01 M154.21 144.65 C194.19 98.45, 235.22 50.5, 278.21 2.01 M159.2 145.01 C201.39 97.64, 239.44 52.54, 283.19 2.37 M159.2 145.01 C196.58 101.2, 234.19 59.04, 283.19 2.37 M164.19 145.37 C204.83 100.56, 245.77 53.99, 288.84 1.98 M164.19 145.37 C194.6 111.64, 224.28 77.56, 288.84 1.98 M169.83 144.98 C216.09 96.8, 258.65 45.93, 293.82 2.34 M169.83 144.98 C198.36 111.93, 225.45 82.13, 293.82 2.34 M174.82 145.34 C222.13 92.22, 266.76 39.3, 299.47 1.94 M174.82 145.34 C214.43 98.2, 254.01 52.59, 299.47 1.94 M180.46 144.94 C206.13 115.61, 232.02 85.17, 304.45 2.3 M180.46 144.94 C208.14 111.98, 236.78 80.02, 304.45 2.3 M185.45 145.31 C211.73 114.99, 239.84 81.36, 310.1 1.91 M185.45 145.31 C211.68 114.43, 238.14 82.89, 310.1 1.91 M191.09 144.91 C218.08 111.04, 246.8 80.32, 315.08 2.27 M191.09 144.91 C214.94 114.57, 239.96 87.29, 315.08 2.27 M196.08 145.27 C239.46 94.21, 286.41 44.83, 320.73 1.88 M196.08 145.27 C229.6 106.98, 264.69 68.01, 320.73 1.88 M201.72 144.88 C228.54 113.1, 258.25 77.79, 325.71 2.24 M201.72 144.88 C243.4 96.39, 285.56 47.13, 325.71 2.24 M206.71 145.24 C253.76 92.02, 298.3 40.23, 331.36 1.84 M206.71 145.24 C244.96 99.96, 284.86 52.62, 331.36 1.84 M212.35 144.84 C256.46 90.35, 305.16 39.74, 336.34 2.2 M212.35 144.84 C254.88 97.2, 294.9 51.16, 336.34 2.2 M217.34 145.2 C260.85 95.12, 303.44 46.91, 341.99 1.81 M217.34 145.2 C254.9 102.38, 292.7 59.98, 341.99 1.81 M222.98 144.81 C255.34 105.33, 290.89 65.64, 346.97 2.17 M222.98 144.81 C266.33 93.19, 310.85 42.12, 346.97 2.17 M227.97 145.17 C263.66 108.29, 298 68.71, 352.62 1.77 M227.97 145.17 C258.23 109.67, 288.39 75.4, 352.62 1.77 M233.61 144.77 C270.05 104.52, 304.97 63.07, 357.6 2.13 M233.61 144.77 C274.47 98.07, 312.89 55.02, 357.6 2.13 M238.6 145.13 C266.67 111.74, 296.18 82.47, 363.25 1.74 M238.6 145.13 C277.04 101.17, 315.84 55.8, 363.25 1.74 M244.24 144.74 C274.9 111.46, 304.37 75.97, 368.23 2.1 M244.24 144.74 C293.37 88.52, 341.04 33.09, 368.23 2.1 M249.23 145.1 C276.79 116.75, 302.14 86.51, 373.88 1.7 M249.23 145.1 C290.94 99.98, 331.11 52.85, 373.88 1.7 M254.87 144.7 C287.13 105.19, 322.18 68.96, 378.86 2.06 M254.87 144.7 C288.12 105.83, 322.32 65.46, 378.86 2.06 M259.86 145.06 C297.18 103.73, 332.56 62.14, 383.85 2.42 M259.86 145.06 C285.47 114.28, 312.5 83.14, 383.85 2.42 M265.5 144.67 C289.04 113.22, 315.35 85.99, 389.49 2.03 M265.5 144.67 C309.3 94.62, 352.05 44.75, 389.49 2.03 M270.49 145.03 C300.51 108.19, 332.82 72.1, 394.48 2.39 M270.49 145.03 C300.97 111.01, 330.93 74.5, 394.48 2.39 M276.13 144.63 C324.36 91.02, 372.28 35.59, 400.12 1.99 M276.13 144.63 C314.28 100.64, 353.55 56.58, 400.12 1.99 M281.12 144.99 C306.5 114.55, 332.17 85.71, 405.11 2.35 M281.12 144.99 C323.82 96.38, 365.49 47.59, 405.11 2.35 M286.1 145.35 C317.3 110.79, 350.4 75.24, 410.75 1.96 M286.1 145.35 C329.26 94.11, 373.2 45.44, 410.75 1.96 M291.75 144.96 C331.46 97.55, 372.96 52.2, 415.74 2.32 M291.75 144.96 C333.75 97.37, 374.48 47.84, 415.74 2.32 M296.73 145.32 C339.74 99.77, 378.75 49.03, 421.38 1.93 M296.73 145.32 C339.27 96.73, 382.31 46.86, 421.38 1.93 M302.38 144.93 C334.79 106.51, 370.65 62.67, 426.37 2.29 M302.38 144.93 C351.86 88.93, 398.72 33.88, 426.37 2.29 M307.36 145.29 C340.53 106.77, 376.08 66.7, 432.01 1.89 M307.36 145.29 C343.7 102.77, 379.62 59.95, 432.01 1.89 M313.01 144.89 C349.35 105.7, 384.17 63.87, 437 2.25 M313.01 144.89 C353.04 98.04, 391.67 55.11, 437 2.25 M317.99 145.25 C339.84 114.7, 366.5 88.66, 442.64 1.86 M317.99 145.25 C351.57 103.84, 387.73 63.05, 442.64 1.86 M323.64 144.86 C348.5 114.38, 375.72 88.33, 447.63 2.22 M323.64 144.86 C355.53 106.62, 389.28 69.15, 447.63 2.22 M328.62 145.22 C372.38 92.62, 416.87 43.42, 452.62 2.58 M328.62 145.22 C366.51 102.9, 405.23 58.43, 452.62 2.58 M334.27 144.82 C363.07 113.17, 389.9 79.51, 456.95 3.69 M334.27 144.82 C367.21 104.1, 402.44 65.76, 456.95 3.69 M339.25 145.18 C372.8 105.31, 407.19 67.1, 461.94 4.05 M339.25 145.18 C370.71 111.13, 401.08 74.54, 461.94 4.05 M344.9 144.79 C373.24 109.42, 403.66 74.09, 464.96 6.68 M344.9 144.79 C380.58 103.48, 416.5 62.22, 464.96 6.68 M349.88 145.15 C396.98 89.22, 441.52 35.96, 469.29 7.79 M349.88 145.15 C384.63 103.87, 421.1 63.92, 469.29 7.79 M355.53 144.75 C384.31 114.2, 411.66 82.06, 471.65 11.17 M355.53 144.75 C395.88 99.08, 436.7 51.19, 471.65 11.17 M360.51 145.11 C388.54 111.66, 414.89 82.27, 474.67 13.79 M360.51 145.11 C398.58 98.72, 438.1 53.79, 474.67 13.79 M366.16 144.72 C399.89 104.57, 433.88 66.68, 475.72 18.68 M366.16 144.72 C408.72 95.09, 451.17 47.53, 475.72 18.68 M371.14 145.08 C397.64 114.94, 427.94 82.11, 477.43 22.82 M371.14 145.08 C410.48 97.21, 450.83 51.08, 477.43 22.82 M376.79 144.68 C397.46 116.7, 419.98 91.38, 479.13 26.95 M376.79 144.68 C400.93 117.38, 426.7 88.5, 479.13 26.95 M381.77 145.04 C417.98 100.88, 455.83 57.26, 479.53 32.59 M381.77 145.04 C410.94 114.25, 438.41 81.7, 479.53 32.59 M387.42 144.65 C412.93 114.6, 438.57 87, 479.27 38.99 M387.42 144.65 C411.38 118.3, 434.26 90.98, 479.27 38.99 M392.4 145.01 C416.3 120.47, 436.57 92.84, 479 45.39 M392.4 145.01 C415.9 120.64, 438.88 93.42, 479 45.39 M397.39 145.37 C415.88 120.12, 437.69 100.01, 479.4 51.03 M397.39 145.37 C419.06 121.26, 438.71 97.02, 479.4 51.03 M403.03 144.98 C418.65 128.5, 434.02 108.59, 479.14 57.43 M403.03 144.98 C431.82 109.79, 463.03 76.44, 479.14 57.43 M408.02 145.34 C423.72 128.07, 441.93 108.83, 479.53 63.07 M408.02 145.34 C423.27 129.37, 437.27 112.48, 479.53 63.07 M413.66 144.94 C430.89 123.63, 448.67 104.59, 479.27 69.47 M413.66 144.94 C429.3 124.99, 447.94 106.36, 479.27 69.47 M418.65 145.3 C429.88 130.69, 442.79 115.76, 479.66 75.11 M418.65 145.3 C438.44 123.35, 457.66 100.29, 479.66 75.11 M424.29 144.91 C443.94 120.3, 467.31 96.47, 479.4 81.51 M424.29 144.91 C443.54 122.53, 464.39 98.64, 479.4 81.51 M429.28 145.27 C440.46 128.53, 453.86 117.18, 479.8 87.15 M429.28 145.27 C446.57 125.43, 465.7 103.67, 479.8 87.15 M434.92 144.87 C452.62 126.78, 470.9 107.72, 479.54 93.55 M434.92 144.87 C446.21 130.49, 458.92 116.32, 479.54 93.55 M439.91 145.23 C454.92 126.59, 470.52 110.64, 479.93 99.19 M439.91 145.23 C454.48 130.43, 466.98 114.01, 479.93 99.19 M445.55 144.84 C459.7 129.63, 473.4 112.45, 479.67 105.59 M445.55 144.84 C456.2 132.48, 466.43 120.71, 479.67 105.59 M451.2 144.44 C457.4 137.27, 463.87 127.4, 478.1 113.5 M451.2 144.44 C457.13 137.53, 462.11 130.98, 478.1 113.5 M457.5 143.29 C464.19 138.07, 467.02 131.58, 476.52 121.41 M457.5 143.29 C461.1 138.38, 465.47 132.12, 476.52 121.41 M465.11 140.63 C469.09 135.97, 469.57 134.23, 476.26 127.8 M465.11 140.63 C467.57 137.52, 470.45 135.62, 476.26 127.8" stroke="#b2f2bb" stroke-width="0.5" fill="none"></path><path d="M32 0 M32 0 C176.04 -2.14, 317.57 -1.78, 445 0 M32 0 C179.36 0.19, 326.13 -0.1, 445 0 M445 0 C466.49 -1.62, 476.74 11.76, 477 32 M445 0 C465.33 0.95, 478.9 10.46, 477 32 M477 32 C478.38 59.54, 477.89 83.51, 477 113 M477 32 C477.4 63.12, 476.78 93.95, 477 113 M477 113 C476.1 134.31, 466.7 143.02, 445 145 M477 113 C478.22 135.1, 467.61 144.46, 445 145 M445 145 C356.12 145.98, 265.85 145.1, 32 145 M445 145 C280.4 145.67, 115.39 145.75, 32 145 M32 145 C8.74 143.12, -0.92 132.86, 0 113 M32 145 C12.94 142.98, -0.76 132.33, 0 113 M0 113 C-0.07 82.2, 0.07 49.78, 0 32 M0 113 C0.34 82.21, 0.32 51.58, 0 32 M0 32 C1.6 12.66, 10.66 -1.83, 32 0 M0 32 C1.88 10.07, 10.34 0.34, 32 0" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g transform="translate(811.46875 230.2578125) rotate(0 220.3125 67.19999999999999)"><text x="0" y="0" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">node:</text><text x="0" y="19.2" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">  directory:</text><text x="0" y="38.4" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">    name: 11111111111111111111111111111111-test</text><text x="0" y="57.599999999999994" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">    digest: &lt;directory-complicated-digest&gt;</text><text x="0" y="76.8" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">    size: 4</text><text x="0" y="96" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">references: []</text><text x="0" y="115.19999999999999" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">narinfo: โ€ฆ</text></g><g stroke-linecap="round" transform="translate(810.1484375 394.59375) rotate(0 238.5 72.5)"><path d="M7.86 8.16 C7.86 8.16, 7.86 8.16, 7.86 8.16 M7.86 8.16 C7.86 8.16, 7.86 8.16, 7.86 8.16 M3.66 19.09 C8.91 13.37, 11.17 9.44, 18.1 2.48 M3.66 19.09 C9.16 12.46, 14.4 7.69, 18.1 2.48 M2.75 26.24 C9.7 18.96, 16.37 8.53, 24.4 1.33 M2.75 26.24 C11.39 17.63, 20.3 6.99, 24.4 1.33 M2.48 32.64 C10.71 24.03, 18.59 13.26, 30.7 0.18 M2.48 32.64 C11.25 23.35, 19.02 14.4, 30.7 0.18 M2.88 38.28 C16.41 22.39, 27.21 9.65, 34.37 2.05 M2.88 38.28 C9.5 30.63, 15.35 23.52, 34.37 2.05 M2.62 44.68 C14.02 30.56, 24.17 18.77, 39.36 2.41 M2.62 44.68 C11.44 34.89, 20.03 24.71, 39.36 2.41 M2.36 51.07 C14.17 38.91, 27.26 20.6, 45 2.02 M2.36 51.07 C14.79 38.21, 27.2 22.82, 45 2.02 M2.75 56.72 C14.28 42.93, 28 31.31, 49.99 2.38 M2.75 56.72 C14.37 45.04, 23.83 32.21, 49.99 2.38 M2.49 63.11 C15.91 47.14, 31.6 33.09, 55.63 1.98 M2.49 63.11 C22.3 40.56, 42.25 16.35, 55.63 1.98 M2.88 68.76 C25.94 42.37, 49.23 17.68, 60.62 2.34 M2.88 68.76 C25.29 41.12, 49.97 16.59, 60.62 2.34 M2.62 75.16 C26.36 49.35, 49.71 23.45, 66.26 1.95 M2.62 75.16 C27.26 46.7, 53.03 16.79, 66.26 1.95 M2.36 81.55 C18.75 60.1, 37.31 41.18, 71.25 2.31 M2.36 81.55 C30.19 50.57, 57.16 19.4, 71.25 2.31 M2.76 87.2 C17.36 71.88, 32.62 50.73, 76.89 1.91 M2.76 87.2 C26.3 60.71, 49.92 34.99, 76.89 1.91 M2.49 93.59 C27.25 66.48, 49.52 37.6, 81.88 2.27 M2.49 93.59 C30.85 62.58, 57.18 29.13, 81.88 2.27 M2.89 99.24 C25.68 73.59, 51.19 46.74, 87.52 1.88 M2.89 99.24 C20.96 78.77, 38.14 58.85, 87.52 1.88 M2.63 105.64 C27.26 82.03, 48.77 55.01, 92.51 2.24 M2.63 105.64 C29.71 75, 56.08 45.73, 92.51 2.24 M2.37 112.03 C38.89 71.73, 73.23 30.36, 98.15 1.85 M2.37 112.03 C31.31 79.87, 58.69 47.79, 98.15 1.85 M2.76 117.68 C38.65 77.93, 73.02 35.37, 103.14 2.21 M2.76 117.68 C39.96 75.26, 75.95 31.69, 103.14 2.21 M3.16 123.32 C39.16 81.11, 77.97 37.18, 108.78 1.81 M3.16 123.32 C26.62 96.63, 50.07 69.49, 108.78 1.81 M4.86 127.45 C38.16 84.21, 75.04 44.41, 113.77 2.17 M4.86 127.45 C26.16 101.72, 50.12 76.16, 113.77 2.17 M5.26 133.1 C32.36 105, 60.22 74.13, 119.41 1.78 M5.26 133.1 C49.95 79.53, 94.95 28.74, 119.41 1.78 M7.62 136.47 C35.36 107.28, 59.43 73.22, 124.4 2.14 M7.62 136.47 C48.07 90.66, 87.09 44.03, 124.4 2.14 M10.64 139.1 C44.23 99.15, 78.94 58.88, 130.04 1.74 M10.64 139.1 C48.85 99.23, 82.98 56.76, 130.04 1.74 M14.31 140.97 C61.07 83.76, 109.13 28.41, 135.03 2.1 M14.31 140.97 C40.23 110.15, 67.08 81.31, 135.03 2.1 M17.33 143.59 C57.88 96.8, 95.53 53.8, 140.67 1.71 M17.33 143.59 C57.08 96.93, 97.56 52.45, 140.67 1.71 M21.01 145.46 C56.01 106.76, 88.35 69.45, 145.66 2.07 M21.01 145.46 C67.32 93, 112.76 41.13, 145.66 2.07 M25.34 146.58 C51.39 115.65, 78.49 84.38, 150.65 2.43 M25.34 146.58 C67.89 94.87, 112.13 43.89, 150.65 2.43 M32.29 144.67 C81.15 91.53, 127.11 36.9, 156.29 2.03 M32.29 144.67 C57.68 113.6, 83.65 83.25, 156.29 2.03 M37.28 145.03 C80.72 91.85, 128.85 42.46, 161.28 2.39 M37.28 145.03 C81.17 93.73, 125.7 40.99, 161.28 2.39 M42.92 144.64 C86.35 93.9, 133.36 38.98, 166.92 2 M42.92 144.64 C81.83 99.95, 121.66 56.09, 166.92 2 M47.91 145 C76.08 112.91, 107.55 78.18, 171.91 2.36 M47.91 145 C81.06 109.66, 111.35 72.82, 171.91 2.36 M52.9 145.36 C100.16 89.62, 149.95 34.51, 177.55 1.96 M52.9 145.36 C89.79 102.8, 125.11 62.89, 177.55 1.96 M58.54 144.96 C90.83 107.72, 120.89 72.28, 182.54 2.32 M58.54 144.96 C83.32 116.05, 108.95 87.3, 182.54 2.32 M63.53 145.32 C97.39 110.22, 128.17 69.65, 188.18 1.93 M63.53 145.32 C97.52 104.23, 131.94 65.28, 188.18 1.93 M69.17 144.93 C100.97 104.36, 136.13 67.64, 193.17 2.29 M69.17 144.93 C105.44 102.82, 142.12 58.58, 193.17 2.29 M74.16 145.29 C113.39 99.23, 154.87 50.02, 198.81 1.9 M74.16 145.29 C115.68 97.91, 154.99 52.02, 198.81 1.9 M79.8 144.9 C117.22 100.44, 155.63 58.46, 203.8 2.26 M79.8 144.9 C119.55 99.87, 159.99 53.61, 203.8 2.26 M84.79 145.26 C114.96 111.31, 144.84 77.07, 209.44 1.86 M84.79 145.26 C130.74 96.04, 174.26 45.02, 209.44 1.86 M90.43 144.86 C119.59 111.08, 146.64 82.67, 214.43 2.22 M90.43 144.86 C137.14 92.65, 182.18 40.05, 214.43 2.22 M95.42 145.22 C134.97 97.52, 174.23 52.21, 220.07 1.83 M95.42 145.22 C121.16 115.94, 147.23 85.62, 220.07 1.83 M101.06 144.83 C128.73 111.86, 158.26 79.88, 225.06 2.19 M101.06 144.83 C127.52 114.11, 154.95 81.91, 225.06 2.19 M106.05 145.19 C132.23 115.04, 157.86 83.38, 230.7 1.79 M106.05 145.19 C133.88 111.98, 162.23 80.52, 230.7 1.79 M111.69 144.79 C135.52 113.89, 160.61 88.04, 235.69 2.15 M111.69 144.79 C155.76 94.05, 201.14 44.16, 235.69 2.15 M116.68 145.15 C149.35 106.9, 185.6 68, 241.33 1.76 M116.68 145.15 C144.46 112.95, 173.88 78.54, 241.33 1.76 M122.32 144.76 C163.47 96.75, 205.64 46.73, 246.32 2.12 M122.32 144.76 C169.21 91.48, 214.25 39.39, 246.32 2.12 M127.31 145.12 C165.46 100.62, 205.52 51.66, 251.96 1.72 M127.31 145.12 C172.07 90.92, 219.78 38.74, 251.96 1.72 M132.95 144.72 C175.8 96.08, 215.01 50.98, 256.95 2.08 M132.95 144.72 C175.65 95.27, 218.21 46.7, 256.95 2.08 M137.94 145.08 C175.05 101.58, 213.52 59.48, 262.59 1.69 M137.94 145.08 C171.08 105.35, 206.42 65.3, 262.59 1.69 M143.58 144.69 C186.3 92.98, 230.97 41.88, 267.58 2.05 M143.58 144.69 C178.59 107.36, 212.3 68.54, 267.58 2.05 M148.57 145.05 C179.15 108.79, 208.33 76.07, 272.56 2.41 M148.57 145.05 C184.5 104.35, 220.01 62.87, 272.56 2.41 M154.21 144.65 C195.56 97.04, 233.34 55.88, 278.21 2.01 M154.21 144.65 C182.1 112.15, 210.74 81.54, 278.21 2.01 M159.2 145.01 C197.45 101.68, 236.26 55.56, 283.19 2.37 M159.2 145.01 C190.06 111.73, 219.71 76.82, 283.19 2.37 M164.19 145.37 C214.12 88.29, 261.36 32.74, 288.84 1.98 M164.19 145.37 C191.17 116.65, 216.4 87.09, 288.84 1.98 M169.83 144.98 C211.86 100.8, 252.07 52.72, 293.82 2.34 M169.83 144.98 C202.9 106.38, 236.88 69.34, 293.82 2.34 M174.82 145.34 C207.63 107.06, 242.55 64.81, 299.47 1.94 M174.82 145.34 C211.8 104.01, 247.25 62.79, 299.47 1.94 M180.46 144.94 C205.47 114.55, 232.3 83.6, 304.45 2.3 M180.46 144.94 C204.45 114.46, 230.06 86.52, 304.45 2.3 M185.45 145.31 C229.35 95.12, 271.86 44.84, 310.1 1.91 M185.45 145.31 C216.37 107.9, 248.61 71.36, 310.1 1.91 M191.09 144.91 C222.55 111.72, 251.85 73.92, 315.08 2.27 M191.09 144.91 C238.55 91.08, 286.27 36.02, 315.08 2.27 M196.08 145.27 C234.29 100.34, 274.54 56.28, 320.73 1.88 M196.08 145.27 C222.42 114.67, 248.24 85.32, 320.73 1.88 M201.72 144.88 C245.02 96.69, 286.1 47.75, 325.71 2.24 M201.72 144.88 C233.01 110.5, 264.89 75.09, 325.71 2.24 M206.71 145.24 C250.09 93.17, 294.8 45.48, 331.36 1.84 M206.71 145.24 C246.95 98.07, 287.81 52.25, 331.36 1.84 M212.35 144.84 C255.02 97.82, 294.21 47.38, 336.34 2.2 M212.35 144.84 C254.78 98.81, 294.78 49.85, 336.34 2.2 M217.34 145.2 C260.68 96.29, 303.76 46.02, 341.99 1.81 M217.34 145.2 C250.78 105.78, 286.51 63.39, 341.99 1.81 M222.98 144.81 C272.96 88.46, 318.86 33.36, 346.97 2.17 M222.98 144.81 C256.46 106.72, 290.95 67.45, 346.97 2.17 M227.97 145.17 C264.79 102.33, 299.75 59.1, 352.62 1.77 M227.97 145.17 C263.52 105.07, 298.61 63.81, 352.62 1.77 M233.61 144.77 C274.12 96.97, 312.37 55.98, 357.6 2.13 M233.61 144.77 C256.24 114.88, 281.92 87.66, 357.6 2.13 M238.6 145.13 C271.28 102.8, 307.76 62.45, 363.25 1.74 M238.6 145.13 C264.06 115.44, 290.29 87.89, 363.25 1.74 M244.24 144.74 C276.08 105.66, 310.55 68.73, 368.23 2.1 M244.24 144.74 C288.01 92.91, 332.32 42.92, 368.23 2.1 M249.23 145.1 C286.85 103.31, 326.12 58.27, 373.88 1.7 M249.23 145.1 C278.25 112.14, 306.08 78.73, 373.88 1.7 M254.87 144.7 C287.46 102.93, 323.86 65.43, 378.86 2.06 M254.87 144.7 C288.68 105.04, 323.05 66.18, 378.86 2.06 M259.86 145.06 C291.28 111.25, 321.61 73.04, 383.85 2.42 M259.86 145.06 C289.7 108.87, 320.7 73.05, 383.85 2.42 M265.5 144.67 C302.2 102.1, 339.7 59.09, 389.49 2.03 M265.5 144.67 C314.09 87.39, 361.48 31.86, 389.49 2.03 M270.49 145.03 C306.79 101.72, 345.32 61.4, 394.48 2.39 M270.49 145.03 C300.53 111.68, 330.35 77.04, 394.48 2.39 M276.13 144.63 C319.2 96.52, 362.81 44.49, 400.12 1.99 M276.13 144.63 C306.01 109.16, 335.36 75.86, 400.12 1.99 M281.12 144.99 C321.76 94.08, 364.67 45.75, 405.11 2.35 M281.12 144.99 C319.22 100.17, 357.51 56.73, 405.11 2.35 M286.1 145.35 C333.71 88.53, 382.13 35.38, 410.75 1.96 M286.1 145.35 C317.78 109.04, 351.73 70.98, 410.75 1.96 M291.75 144.96 C337.19 88.4, 384.3 34.77, 415.74 2.32 M291.75 144.96 C317.91 112.26, 345.28 81.05, 415.74 2.32 M296.73 145.32 C325.68 112.17, 358.06 76.44, 421.38 1.93 M296.73 145.32 C343.84 88.98, 392.35 33.13, 421.38 1.93 M302.38 144.93 C339.95 106.31, 374.33 64.87, 426.37 2.29 M302.38 144.93 C336.03 105.32, 370.06 67.12, 426.37 2.29 M307.36 145.29 C340.16 110, 371.15 72.35, 432.01 1.89 M307.36 145.29 C340.08 109.03, 371.38 70.88, 432.01 1.89 M313.01 144.89 C346.7 110.4, 379.86 70.69, 437 2.25 M313.01 144.89 C342.6 108.31, 373.8 74.9, 437 2.25 M317.99 145.25 C351.16 108.99, 380.33 72.15, 442.64 1.86 M317.99 145.25 C343.7 117.08, 369.38 86.42, 442.64 1.86 M323.64 144.86 C370.25 87.28, 421.75 33.13, 447.63 2.22 M323.64 144.86 C351.37 114.11, 380.44 81.53, 447.63 2.22 M328.62 145.22 C355.29 117.71, 379.5 88.31, 452.62 2.58 M328.62 145.22 C362.76 106.45, 396.24 68.64, 452.62 2.58 M334.27 144.82 C363.38 107.43, 398.48 72.68, 456.95 3.69 M334.27 144.82 C357.91 116.46, 383 87.56, 456.95 3.69 M339.25 145.18 C379.04 101.04, 417.7 54.68, 461.94 4.05 M339.25 145.18 C385.54 91.6, 433.51 37.95, 461.94 4.05 M344.9 144.79 C386.94 95.92, 432.15 44.1, 464.96 6.68 M344.9 144.79 C374.61 108.52, 405.83 74.64, 464.96 6.68 M349.88 145.15 C391 98.05, 435.67 47.17, 469.29 7.79 M349.88 145.15 C395.91 94.65, 441.86 42.78, 469.29 7.79 M355.53 144.75 C385.45 107.18, 418.33 70.28, 471.65 11.17 M355.53 144.75 C400.44 93.54, 444.91 43.39, 471.65 11.17 M360.51 145.11 C401.24 102.6, 437.59 56.72, 474.67 13.79 M360.51 145.11 C405.18 92.61, 450.73 39.5, 474.67 13.79 M366.16 144.72 C400.27 105.26, 433.48 67.04, 475.72 18.68 M366.16 144.72 C393.53 111.8, 421.43 78.48, 475.72 18.68 M371.14 145.08 C393.65 118.74, 414.53 93.08, 477.43 22.82 M371.14 145.08 C402.07 111.49, 430.19 77.53, 477.43 22.82 M376.79 144.68 C398.96 116.96, 423.02 86.8, 479.13 26.95 M376.79 144.68 C405.71 112.24, 431.87 81.46, 479.13 26.95 M381.77 145.04 C402.53 120.66, 424.65 98.55, 479.53 32.59 M381.77 145.04 C415.6 104.73, 447.91 66.04, 479.53 32.59 M387.42 144.65 C421.09 106.74, 452.24 68, 479.27 38.99 M387.42 144.65 C417.09 111.82, 445.22 80.57, 479.27 38.99 M392.4 145.01 C416.08 117.03, 443.83 87.54, 479 45.39 M392.4 145.01 C412.54 120.34, 434.35 95.05, 479 45.39 M397.39 145.37 C418.8 119.7, 440.1 98.63, 479.4 51.03 M397.39 145.37 C425.95 112.73, 456.43 79.45, 479.4 51.03 M403.03 144.98 C425.28 120.65, 446.29 95.66, 479.14 57.43 M403.03 144.98 C425.92 117.76, 450.02 91.73, 479.14 57.43 M408.02 145.34 C425.07 128.2, 439.73 110.85, 479.53 63.07 M408.02 145.34 C430.26 120.38, 450.08 96.27, 479.53 63.07 M413.66 144.94 C435.93 123.3, 454.67 96.07, 479.27 69.47 M413.66 144.94 C439.06 116.88, 463.51 87.09, 479.27 69.47 M418.65 145.3 C434.28 127.3, 448 113.58, 479.66 75.11 M418.65 145.3 C439.53 121.35, 457.99 99.06, 479.66 75.11 M424.29 144.91 C438.64 130.23, 450.19 116.95, 479.4 81.51 M424.29 144.91 C446.16 119.97, 466.98 96.04, 479.4 81.51 M429.28 145.27 C449.48 121.21, 467.73 98.87, 479.8 87.15 M429.28 145.27 C445.11 126.77, 460.67 109.04, 479.8 87.15 M434.92 144.87 C451.5 125.37, 470.64 107.76, 479.54 93.55 M434.92 144.87 C450.33 126.77, 466.57 106.69, 479.54 93.55 M439.91 145.23 C452.13 132.68, 464.37 120.59, 479.93 99.19 M439.91 145.23 C451.04 133.55, 462.09 121.11, 479.93 99.19 M445.55 144.84 C458.79 129.03, 473.86 114.16, 479.67 105.59 M445.55 144.84 C454.8 134.14, 463.69 123.97, 479.67 105.59 M451.2 144.44 C462.63 132.85, 473.14 119.83, 478.1 113.5 M451.2 144.44 C458.17 135.43, 463.94 127.69, 478.1 113.5 M457.5 143.29 C463.11 139.03, 465.29 134.84, 476.52 121.41 M457.5 143.29 C464 136.22, 469.37 130.32, 476.52 121.41 M465.11 140.63 C467.6 137.97, 471.38 132.46, 476.26 127.8 M465.11 140.63 C467.66 137.17, 471.15 133.88, 476.26 127.8" stroke="#b2f2bb" stroke-width="0.5" fill="none"></path><path d="M32 0 M32 0 C142.9 2.12, 251.08 2.36, 445 0 M32 0 C115.5 -0.6, 199.1 -1.14, 445 0 M445 0 C467.37 0.9, 475.89 9.1, 477 32 M445 0 C464.69 1.75, 476.83 12.79, 477 32 M477 32 C475.75 51.18, 476.7 69.4, 477 113 M477 32 C477.02 52.65, 476.92 71.83, 477 113 M477 113 C475.11 133.14, 465.24 146.78, 445 145 M477 113 C478.86 132.08, 465.62 144.87, 445 145 M445 145 C290.97 146.31, 137.09 146.13, 32 145 M445 145 C337.93 143.89, 230.99 144.81, 32 145 M32 145 C9.61 145.84, -0.8 134.99, 0 113 M32 145 C11.35 145.42, 1.86 135.37, 0 113 M0 113 C1.15 86.4, 0.56 57.51, 0 32 M0 113 C-0.77 88.92, -1.37 66.73, 0 32 M0 32 C1.21 9.29, 10.51 -1.67, 32 0 M0 32 C2.23 9.98, 9.94 -0.68, 32 0" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g transform="translate(815.1484375 399.59375) rotate(0 220.3125 67.19999999999999)"><text x="0" y="0" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">node:</text><text x="0" y="19.2" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">  directory:</text><text x="0" y="38.4" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">    name: 22222222222222222222222222222222-test</text><text x="0" y="57.599999999999994" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">    digest: &lt;directory-with-keep-digest&gt;</text><text x="0" y="76.8" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">    size: 1</text><text x="0" y="96" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">references: []</text><text x="0" y="115.19999999999999" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">narinfo: โ€ฆ</text></g><g stroke-linecap="round"><g transform="translate(935.83984375 297.9609375) rotate(0 -127.17742444525584 -6.3835650656164376)"><path d="M-0.07 0.59 C-42.41 -1.51, -211.36 -11.21, -253.47 -13.35 M-1.57 -0.15 C-44.1 -1.99, -212.39 -9.54, -254.28 -11.88" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g transform="translate(935.83984375 297.9609375) rotate(0 -127.17742444525584 -6.3835650656164376)"><path d="M-224.64 -20.31 C-235.83 -15.98, -244.05 -15.65, -255.88 -12.33 M-225.16 -19.94 C-234.34 -18.84, -244.43 -15.58, -254.5 -12.11" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g transform="translate(935.83984375 297.9609375) rotate(0 -127.17742444525584 -6.3835650656164376)"><path d="M-225.65 0.18 C-236.36 -2.61, -244.23 -9.41, -255.88 -12.33 M-226.18 0.55 C-234.92 -4.92, -244.69 -8.23, -254.5 -12.11" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g></g><mask></mask><g stroke-linecap="round"><g transform="translate(934.54296875 464.29296875) rotate(0 -122.96248760393826 -133.71583716869355)"><path d="M-0.84 -0.49 C-41.65 -45.05, -205 -222.75, -245.8 -266.94 M0.91 -1.8 C-39.98 -46.25, -205.61 -221.44, -246.84 -265.75" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g transform="translate(934.54296875 464.29296875) rotate(0 -122.96248760393826 -133.71583716869355)"><path d="M-220.88 -252.52 C-227.08 -255.42, -230.79 -256.62, -246.15 -266.29 M-219.26 -252.47 C-226.67 -255.03, -232.47 -258.86, -246.62 -266.61" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g transform="translate(934.54296875 464.29296875) rotate(0 -122.96248760393826 -133.71583716869355)"><path d="M-235.85 -238.47 C-238.83 -244.34, -239.26 -248.61, -246.15 -266.29 M-234.23 -238.42 C-238.04 -244.21, -240.25 -251.41, -246.62 -266.61" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g></g><mask></mask><g stroke-linecap="round" transform="translate(808.1090530561523 569.6183182417908) rotate(0 238.5 82)"><path d="M7.86 8.16 C7.86 8.16, 7.86 8.16, 7.86 8.16 M7.86 8.16 C7.86 8.16, 7.86 8.16, 7.86 8.16 M3.66 19.09 C6.81 13.38, 14.57 7.61, 18.1 2.48 M3.66 19.09 C7.86 14.77, 10.94 9.38, 18.1 2.48 M2.75 26.24 C8.42 17.74, 14.61 10.75, 24.4 1.33 M2.75 26.24 C7.35 20.79, 11.06 16.63, 24.4 1.33 M2.48 32.64 C8.41 23.44, 17.32 15.14, 30.7 0.18 M2.48 32.64 C8.74 25.77, 13.03 20.51, 30.7 0.18 M2.88 38.28 C8.15 29.11, 19.49 21.57, 34.37 2.05 M2.88 38.28 C13.52 25.23, 25.55 13.16, 34.37 2.05 M2.62 44.68 C16 33.11, 26.07 16.01, 39.36 2.41 M2.62 44.68 C14.25 32.19, 24.76 18.26, 39.36 2.41 M2.36 51.07 C18.74 34.45, 31.43 18.56, 45 2.02 M2.36 51.07 C17.04 34.98, 31.27 17.88, 45 2.02 M2.75 56.72 C12.45 43.26, 26.24 32.38, 49.99 2.38 M2.75 56.72 C13.08 44.38, 23.25 35.02, 49.99 2.38 M2.49 63.11 C13.22 50.48, 25.3 38, 55.63 1.98 M2.49 63.11 C20.59 44.59, 36.16 24.18, 55.63 1.98 M2.88 68.76 C22.9 48.96, 39.83 27.4, 60.62 2.34 M2.88 68.76 C24.33 43.11, 46.62 19.91, 60.62 2.34 M2.62 75.16 C24.58 48.11, 49.17 22.73, 66.26 1.95 M2.62 75.16 C17.85 56.46, 36.34 37, 66.26 1.95 M2.36 81.55 C22.67 56.14, 44.58 32.75, 71.25 2.31 M2.36 81.55 C28.06 52.26, 52.4 23.27, 71.25 2.31 M2.76 87.2 C23.08 61.22, 45.46 37.01, 76.89 1.91 M2.76 87.2 C19.7 68.52, 35.9 49.32, 76.89 1.91 M2.49 93.59 C20.89 69.28, 40.59 49.24, 81.88 2.27 M2.49 93.59 C22.48 70.61, 42.67 48.73, 81.88 2.27 M2.89 99.24 C34.82 63.55, 63.23 31.57, 87.52 1.88 M2.89 99.24 C20.63 78.39, 39.49 56.84, 87.52 1.88 M2.63 105.64 C35.22 68.03, 69.69 27.76, 92.51 2.24 M2.63 105.64 C33.18 70.17, 62.24 35.99, 92.51 2.24 M2.37 112.03 C26.3 85.12, 50.21 57.23, 98.15 1.85 M2.37 112.03 C36.74 71.78, 71.12 32.15, 98.15 1.85 M2.76 117.68 C37.79 74.87, 77.39 32.27, 103.14 2.21 M2.76 117.68 C28.95 87.13, 55.3 54.97, 103.14 2.21 M2.5 124.07 C29.71 93.79, 56.39 67.33, 108.78 1.81 M2.5 124.07 C39.16 80.77, 76.22 37.93, 108.78 1.81 M2.89 129.72 C46.1 83.46, 86.14 32.26, 113.77 2.17 M2.89 129.72 C38.33 90.23, 73.3 50.35, 113.77 2.17 M2.63 136.11 C32.42 101.51, 64.36 66.19, 119.41 1.78 M2.63 136.11 C42.9 88.38, 84.51 40.74, 119.41 1.78 M3.03 141.76 C47.01 92.79, 88.14 44.94, 124.4 2.14 M3.03 141.76 C36.31 103.57, 71.35 65.73, 124.4 2.14 M4.73 145.89 C51.61 90.8, 99.17 38.12, 130.04 1.74 M4.73 145.89 C47.88 98.67, 90.06 49.91, 130.04 1.74 M5.13 151.53 C50.72 98.34, 95.33 50.22, 135.03 2.1 M5.13 151.53 C54.14 93.61, 105.46 35.23, 135.03 2.1 M7.49 154.91 C46.02 110.99, 81.47 70.85, 140.67 1.71 M7.49 154.91 C52.42 103.68, 96.67 51.91, 140.67 1.71 M10.51 157.54 C60.56 98.74, 112.31 43.05, 145.66 2.07 M10.51 157.54 C41.53 122.75, 73.01 84.79, 145.66 2.07 M13.53 160.16 C39.75 129.1, 69.49 95.86, 150.65 2.43 M13.53 160.16 C66.46 100.75, 117.42 41, 150.65 2.43 M17.2 162.03 C70.82 99.54, 124.51 39.14, 156.29 2.03 M17.2 162.03 C45.6 127.78, 75.59 94.57, 156.29 2.03 M20.88 163.9 C56.65 126.92, 91.29 87.42, 161.28 2.39 M20.88 163.9 C71.96 105.59, 125.36 44.09, 161.28 2.39 M25.21 165.02 C65.92 117.85, 106.95 72.45, 166.92 2 M25.21 165.02 C60.62 122.61, 96.68 82.03, 166.92 2 M30.2 165.38 C61.83 128.51, 95.39 92.16, 171.91 2.36 M30.2 165.38 C61.34 132.24, 90.7 98.68, 171.91 2.36 M34.53 166.49 C75.36 119.62, 116.91 72.06, 177.55 1.96 M34.53 166.49 C82.24 112.82, 128.51 59.11, 177.55 1.96 M40.17 166.1 C71.9 131.05, 100.75 95.32, 182.54 2.32 M40.17 166.1 C75.59 123.88, 112.97 81.96, 182.54 2.32 M45.16 166.46 C101.91 101.55, 158.7 35.39, 188.18 1.93 M45.16 166.46 C92.93 108.21, 142.16 51.42, 188.18 1.93 M50.8 166.06 C83.66 126.11, 118.37 86.9, 193.17 2.29 M50.8 166.06 C90.57 121.95, 130.52 76.87, 193.17 2.29 M55.79 166.42 C98.61 114.56, 145.49 65.61, 198.81 1.9 M55.79 166.42 C89.92 128.89, 122.15 90.21, 198.81 1.9 M61.43 166.03 C108.14 112.25, 154.28 57.71, 203.8 2.26 M61.43 166.03 C102.94 116.29, 146.47 66.47, 203.8 2.26 M66.42 166.39 C110.46 115.14, 155.39 64.89, 209.44 1.86 M66.42 166.39 C103.96 123.93, 141.14 82.62, 209.44 1.86 M72.06 165.99 C116.55 115.54, 157.91 65.86, 214.43 2.22 M72.06 165.99 C122.43 107.95, 174.9 47.56, 214.43 2.22 M77.05 166.35 C130.59 105.53, 183.67 43.12, 220.07 1.83 M77.05 166.35 C125.26 111.87, 172.24 58.31, 220.07 1.83 M82.69 165.96 C137.84 105.94, 191.52 41.79, 225.06 2.19 M82.69 165.96 C133.74 107.43, 183.36 50.36, 225.06 2.19 M87.68 166.32 C128.09 119.78, 167.53 73.55, 230.7 1.79 M87.68 166.32 C125.69 123.08, 162.79 79.78, 230.7 1.79 M92.67 166.68 C142.83 109.77, 193.44 51.39, 235.69 2.15 M92.67 166.68 C144.44 108.24, 195.97 47.04, 235.69 2.15 M98.31 166.28 C138.71 115.99, 180.28 69.93, 241.33 1.76 M98.31 166.28 C147.53 110.62, 196.35 53.63, 241.33 1.76 M103.3 166.64 C137.9 125.59, 173.89 87.87, 246.32 2.12 M103.3 166.64 C156.08 106.69, 208.6 46.04, 246.32 2.12 M108.94 166.25 C148.42 122.93, 186.31 81.16, 251.96 1.72 M108.94 166.25 C161 105.15, 212.81 45.73, 251.96 1.72 M113.93 166.61 C141.79 133.21, 172.21 98.82, 256.95 2.08 M113.93 166.61 C156.91 116.88, 202.51 64.75, 256.95 2.08 M119.57 166.21 C168.54 109.97, 219.75 52.97, 262.59 1.69 M119.57 166.21 C167.8 108.87, 219.02 51.26, 262.59 1.69 M124.56 166.57 C161.75 123.88, 199.53 80.79, 267.58 2.05 M124.56 166.57 C156.23 128.01, 190.3 88.85, 267.58 2.05 M130.2 166.18 C169.64 122.3, 211.11 75.14, 272.56 2.41 M130.2 166.18 C168.9 120.31, 208.25 76.02, 272.56 2.41 M135.19 166.54 C184.84 111.09, 234.19 52.53, 278.21 2.01 M135.19 166.54 C187.36 108.27, 238.69 50.75, 278.21 2.01 M140.83 166.15 C183.77 117.65, 225.92 69.25, 283.19 2.37 M140.83 166.15 C187.53 113.68, 233.77 60.45, 283.19 2.37 M145.82 166.51 C182.78 122.72, 219.21 80.97, 288.84 1.98 M145.82 166.51 C200.67 105.62, 254.4 42.05, 288.84 1.98 M151.46 166.11 C182.26 131.38, 212.83 96.05, 293.82 2.34 M151.46 166.11 C186.45 126.29, 219.87 88.44, 293.82 2.34 M156.45 166.47 C204.97 108.71, 255.45 52.85, 299.47 1.94 M156.45 166.47 C211.25 105.36, 265.8 42.21, 299.47 1.94 M162.09 166.08 C213.47 106.17, 266.91 43.59, 304.45 2.3 M162.09 166.08 C216.39 105.62, 269.56 44.48, 304.45 2.3 M167.08 166.44 C219.65 106.64, 271.52 49.11, 310.1 1.91 M167.08 166.44 C214.13 111.77, 261.4 58.06, 310.1 1.91 M172.72 166.04 C208.84 124.53, 247 83.41, 315.08 2.27 M172.72 166.04 C205.15 129.49, 236.42 90.2, 315.08 2.27 M177.71 166.4 C209.21 130.02, 243.57 90.18, 320.73 1.88 M177.71 166.4 C231.87 105.57, 287.31 42.39, 320.73 1.88 M183.35 166.01 C220.85 120.46, 259.07 75.83, 325.71 2.24 M183.35 166.01 C230.79 111.91, 277 57.33, 325.71 2.24 M188.34 166.37 C233.45 112.01, 280.56 61.66, 331.36 1.84 M188.34 166.37 C240.12 108.18, 290.83 49.09, 331.36 1.84 M193.98 165.97 C226.25 131.55, 256.45 92.17, 336.34 2.2 M193.98 165.97 C244.44 109.17, 294.38 52.76, 336.34 2.2 M198.97 166.33 C247.81 112.21, 292.15 60.33, 341.99 1.81 M198.97 166.33 C239.08 119.2, 279.09 71.05, 341.99 1.81 M203.95 166.69 C258.46 101.41, 315.11 40.21, 346.97 2.17 M203.95 166.69 C257.24 106.78, 308.75 46.24, 346.97 2.17 M209.6 166.3 C264.45 102.42, 321.03 37.17, 352.62 1.77 M209.6 166.3 C247.96 124.81, 283.55 84.13, 352.62 1.77 M214.58 166.66 C251.99 125.65, 288.37 82.49, 357.6 2.13 M214.58 166.66 C245.51 132.68, 277.5 95.94, 357.6 2.13 M220.23 166.26 C250.91 130.54, 281.47 95.54, 363.25 1.74 M220.23 166.26 C270.46 109.08, 319.88 50.52, 363.25 1.74 M225.21 166.62 C276.61 104.17, 331.06 44.88, 368.23 2.1 M225.21 166.62 C275.98 109.32, 326.13 50.94, 368.23 2.1 M230.86 166.23 C272.47 114.76, 315.56 64.33, 373.88 1.7 M230.86 166.23 C278.29 112.46, 325.09 57.98, 373.88 1.7 M235.84 166.59 C282.63 113.09, 329.86 58.03, 378.86 2.06 M235.84 166.59 C289.28 107.3, 342.41 45.74, 378.86 2.06 M241.49 166.2 C289.5 107.98, 340.13 49.49, 383.85 2.42 M241.49 166.2 C291 108.5, 340.73 52, 383.85 2.42 M246.47 166.56 C299.38 107.83, 352.11 46.7, 389.49 2.03 M246.47 166.56 C290.21 118.16, 331.91 67.65, 389.49 2.03 M252.12 166.16 C287.75 124.26, 324.97 81.73, 394.48 2.39 M252.12 166.16 C285.46 128.24, 318.01 91.66, 394.48 2.39 M257.1 166.52 C303.62 111.42, 349.89 60.83, 400.12 1.99 M257.1 166.52 C297.83 119.09, 339.11 70.77, 400.12 1.99 M262.75 166.13 C302.79 119.22, 343.84 71.97, 405.11 2.35 M262.75 166.13 C318.14 102.81, 374.01 39.26, 405.11 2.35 M267.73 166.49 C316.44 106.41, 370.98 48.7, 410.75 1.96 M267.73 166.49 C314.68 110.39, 365.02 54.21, 410.75 1.96 M273.38 166.09 C319.98 112.15, 368.66 57.51, 415.74 2.32 M273.38 166.09 C318.87 114.27, 364 62.75, 415.74 2.32 M278.36 166.45 C334.32 101.65, 390.55 37.39, 421.38 1.93 M278.36 166.45 C333.03 105.94, 385.98 44.74, 421.38 1.93 M284.01 166.06 C317.55 130.16, 348.17 93.82, 426.37 2.29 M284.01 166.06 C326.17 119.01, 367.32 70.57, 426.37 2.29 M288.99 166.42 C322.48 126.3, 360.56 85.92, 432.01 1.89 M288.99 166.42 C335.34 112.09, 384.1 55.77, 432.01 1.89 M294.64 166.02 C329.61 122.3, 366.47 79.8, 437 2.25 M294.64 166.02 C335.35 119.83, 373.64 74.32, 437 2.25 M299.62 166.38 C347.99 112.19, 397.82 52.47, 442.64 1.86 M299.62 166.38 C346.57 111.86, 394.7 58.56, 442.64 1.86 M305.27 165.99 C340.79 125.39, 378.2 81.4, 447.63 2.22 M305.27 165.99 C336.56 132.13, 367.18 97.05, 447.63 2.22 M310.25 166.35 C364.09 102.48, 420.3 41.58, 452.62 2.58 M310.25 166.35 C350.18 121.57, 389.7 76.26, 452.62 2.58 M315.9 165.95 C355.72 120.42, 398.24 73.94, 456.95 3.69 M315.9 165.95 C361.7 110.74, 408.98 56.77, 456.95 3.69 M320.88 166.31 C359.3 121.21, 397.93 79.49, 461.94 4.05 M320.88 166.31 C351.82 129.41, 382.91 93.48, 461.94 4.05 M325.87 166.67 C377.05 109.95, 427.19 53.77, 464.96 6.68 M325.87 166.67 C359.4 128.68, 392.04 90.92, 464.96 6.68 M331.51 166.28 C388.57 102.78, 442.05 38.29, 469.29 7.79 M331.51 166.28 C365.47 127.01, 402.68 86.23, 469.29 7.79 M336.5 166.64 C378.68 118.85, 420.7 69.51, 471.65 11.17 M336.5 166.64 C367.77 133.94, 395.41 99.18, 471.65 11.17 M342.14 166.25 C389.11 113.09, 435.96 59.54, 474.67 13.79 M342.14 166.25 C370.01 136.08, 397.01 102.54, 474.67 13.79 M347.13 166.61 C393.05 113.37, 435.84 64.96, 475.72 18.68 M347.13 166.61 C393.9 112.62, 438.68 61.59, 475.72 18.68 M352.77 166.21 C380.68 136.97, 404.58 109.35, 477.43 22.82 M352.77 166.21 C393.66 118.4, 432.23 72.18, 477.43 22.82 M357.76 166.57 C393.89 123.75, 430.73 84.47, 479.13 26.95 M357.76 166.57 C407.4 110.77, 454.79 55.34, 479.13 26.95 M363.4 166.18 C388.23 138.89, 413.94 108.18, 479.53 32.59 M363.4 166.18 C409.58 114.51, 453.68 63.43, 479.53 32.59 M368.39 166.54 C408.72 121.43, 446.29 77.48, 479.27 38.99 M368.39 166.54 C401.91 127.96, 434.91 89.62, 479.27 38.99 M374.03 166.14 C412.5 122.98, 449.36 75.75, 479 45.39 M374.03 166.14 C396.44 139.53, 422.02 110.62, 479 45.39 M379.02 166.5 C409.99 128.35, 442.35 96.32, 479.4 51.03 M379.02 166.5 C406.86 135.7, 432.68 106.1, 479.4 51.03 M384.66 166.11 C402.24 145.5, 422.81 122.74, 479.14 57.43 M384.66 166.11 C422.18 124.54, 458.93 82.58, 479.14 57.43 M389.65 166.47 C419.28 131.51, 452.05 96.59, 479.53 63.07 M389.65 166.47 C419.89 132.79, 448.41 99.82, 479.53 63.07 M395.29 166.07 C426.82 132.48, 455.45 96.46, 479.27 69.47 M395.29 166.07 C416.92 140.62, 438.2 116.72, 479.27 69.47 M400.28 166.43 C420.47 143.05, 442.61 120.52, 479.66 75.11 M400.28 166.43 C426.13 136.5, 452.84 104.75, 479.66 75.11 M405.92 166.04 C427.91 140.97, 448.1 117.36, 479.4 81.51 M405.92 166.04 C431.73 136.16, 458.33 107.47, 479.4 81.51 M410.91 166.4 C439.08 133.08, 467.49 104.88, 479.8 87.15 M410.91 166.4 C439.78 134.31, 465.58 102.73, 479.8 87.15 M416.55 166 C435.71 144.16, 456.89 119.23, 479.54 93.55 M416.55 166 C429.32 151.02, 443.37 135.34, 479.54 93.55 M421.54 166.36 C441.2 143.55, 460.71 119.18, 479.28 99.95 M421.54 166.36 C436.83 150.09, 450.82 132.4, 479.28 99.95 M427.18 165.97 C444.74 144.68, 466.03 122.82, 479.67 105.59 M427.18 165.97 C445.95 144.28, 463.23 123.49, 479.67 105.59 M432.17 166.33 C443.24 153.78, 457.62 140.63, 479.41 111.99 M432.17 166.33 C444.12 153.05, 454.84 140.71, 479.41 111.99 M437.16 166.69 C445.39 157.04, 457.46 143.05, 479.8 117.63 M437.16 166.69 C448.39 153.72, 458.12 143.15, 479.8 117.63 M442.8 166.29 C452.3 154.17, 462.42 144.1, 479.54 124.03 M442.8 166.29 C455.94 150.05, 469.26 134.36, 479.54 124.03 M447.79 166.65 C456.94 155.72, 466.27 142.82, 477.31 132.69 M447.79 166.65 C458.81 155.08, 470.45 141.43, 477.31 132.69 M454.09 165.51 C459.1 157.63, 466.97 149.2, 476.39 139.85 M454.09 165.51 C460.38 158.66, 465.45 152.71, 476.39 139.85 M464.32 159.83 C469.65 156.05, 473.41 148.37, 475.48 147 M464.32 159.83 C466.17 157.25, 468.66 154.88, 475.48 147" stroke="#b2f2bb" stroke-width="0.5" fill="none"></path><path d="M32 0 M32 0 C179.89 -1.74, 328.07 0.08, 445 0 M32 0 C192.02 0.21, 350.62 -0.17, 445 0 M445 0 C465.52 0.25, 477.14 11.05, 477 32 M445 0 C467.31 -0.47, 475.37 11.81, 477 32 M477 32 C475.83 66.77, 477.32 98.5, 477 132 M477 32 C477.31 66.84, 477.55 102.41, 477 132 M477 132 C477.4 152.02, 464.97 162.99, 445 164 M477 132 C478.29 151.95, 467.37 166.29, 445 164 M445 164 C309.65 164.67, 175.86 163.51, 32 164 M445 164 C339.17 163.73, 232.87 164.46, 32 164 M32 164 C9.7 164.07, -1.59 152.38, 0 132 M32 164 C9.56 164.33, 1.77 155.2, 0 132 M0 132 C1.13 92.98, -0.18 57.12, 0 32 M0 132 C0.33 111.2, 1.53 90.02, 0 32 M0 32 C-1.62 11.04, 10.67 1.54, 32 0 M0 32 C0.37 11.97, 12.23 -2.15, 32 0" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g transform="translate(813.1090530561523 574.6183182417908) rotate(0 220.3125 76.80000000000001)"><text x="0" y="0" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">node:</text><text x="0" y="19.2" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">  file:</text><text x="0" y="38.4" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">    name: 33333333333333333333333333333333-test</text><text x="0" y="57.599999999999994" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">    digest: &lt;blob-a-digest&gt;</text><text x="0" y="76.8" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">    size: 3</text><text x="0" y="96" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">    executable: false</text><text x="0" y="115.19999999999999" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">references: []</text><text x="0" y="134.4" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#1e1e1e" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">narinfo: โ€ฆ</text></g><g stroke-linecap="round"><g transform="translate(942.4901398439579 641.0079592022902) rotate(0 -391.38489601802087 -282.46266052120785)"><path d="M-0.12 0.36 C-130.24 -93.43, -651.31 -469.22, -781.67 -563.32 M-1.64 -0.49 C-131.84 -94.62, -652.36 -471.5, -782.65 -565.29" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g transform="translate(942.4901398439579 641.0079592022902) rotate(0 -391.38489601802087 -282.46266052120785)"><path d="M-753.18 -557.22 C-759.58 -560.05, -767.44 -560.17, -782.06 -566.37 M-754.28 -557.06 C-760.35 -558.69, -767.09 -560.3, -781.67 -564.75" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g transform="translate(942.4901398439579 641.0079592022902) rotate(0 -391.38489601802087 -282.46266052120785)"><path d="M-765.2 -540.58 C-768.89 -547.05, -774.11 -550.81, -782.06 -566.37 M-766.3 -540.42 C-769.51 -545.96, -773.48 -551.41, -781.67 -564.75" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g></g><mask></mask></svg>
\ No newline at end of file
diff --git a/users/flokli/presentations/2023-09-09-nixcon-tvix/tvixbolt.webm b/users/flokli/presentations/2023-09-09-nixcon-tvix/tvixbolt.webm
new file mode 100644
index 0000000000..69bd20f193
--- /dev/null
+++ b/users/flokli/presentations/2023-09-09-nixcon-tvix/tvixbolt.webm
Binary files differdiff --git a/users/flokli/presentations/2023-09-13-asg-tvix-store/default.nix b/users/flokli/presentations/2023-09-13-asg-tvix-store/default.nix
new file mode 100644
index 0000000000..840f21de81
--- /dev/null
+++ b/users/flokli/presentations/2023-09-13-asg-tvix-store/default.nix
@@ -0,0 +1,32 @@
+{ depot, pkgs, ... }:
+
+let
+  inherit (pkgs)
+    fontconfig qrencode runCommand stdenv;
+  mkQr = url: runCommand "qrcode.png" { } ''
+    ${qrencode}/bin/qrencode -o $out -t SVG -s 5 \
+      --background=fafafa \
+      --foreground=000000 \
+      ${url}
+  '';
+in
+stdenv.mkDerivation {
+  name = "2023-asg-tvix-store";
+  src = ./.;
+
+  FONTCONFIG_FILE = pkgs.makeFontsConf {
+    fontDirectories = with pkgs; [ jetbrains-mono fira fira-code fira-mono lato ];
+  };
+
+  nativeBuildInputs = [ pkgs.reveal-md pkgs.graphviz ];
+
+  buildPhase = ''
+    cp ${depot.tvix.logo}/logo.png tvix-logo.png
+    cp ${mkQr "https://flokli.de"} qrcode-flokli.svg
+    cp ${mkQr "https://tvix.dev"} qrcode-tvix.svg
+
+    mkdir -p $out
+    cp tvix-store-graph-blob-directory.svg $out/
+    reveal-md --static $out presentation.md
+  '';
+}
diff --git a/users/flokli/presentations/2023-09-13-asg-tvix-store/presentation.md b/users/flokli/presentations/2023-09-13-asg-tvix-store/presentation.md
new file mode 100644
index 0000000000..978934f9a4
--- /dev/null
+++ b/users/flokli/presentations/2023-09-13-asg-tvix-store/presentation.md
@@ -0,0 +1,138 @@
+---
+author:
+- Florian Klink
+date: 2023-09-09
+title: "tvix-store: A content-addressed file system and sync protocol"
+theme: moon
+revealOptions:
+  transition: 'fade'
+---
+
+## tvix-store
+### A content-addressed file system and sync protocol
+
+2023-09-13
+
+Florian Klink / flokli
+
+---
+
+## Whoami
+
+- <!-- .element: class="fragment" -->
+  flokli
+- <!-- .element: class="fragment" -->
+  Nix/NixOS contributor
+  - maintain systemd, nss and more low-level stuff there
+- <!-- .element: class="fragment" -->
+  Freelance Nix/DevOps consultant
+
+Note: more Kubernetes/DevOps exposure with work
+
+---
+
+## What is tvix-store?
+- <!-- .element: class="fragment" -->
+  A new implementation of a content-addressed "storage system"
+  - <!-- .element: class="fragment" -->
+    part of the Tvix Project, a (WIP) reimplementation of Nix and auxillary components in Rust
+  - <!-- .element: class="fragment" -->
+    Storage model: think about git trees and its Merkle DAGโ€ฆ
+  - <!-- .element: class="fragment" -->
+    โ€ฆ but with nicer wire format (`.proto`) and hash function (blake3)
+
+---
+
+## Storage model
+- <!-- .element: class="fragment" -->
+  Once you know the root: everything else is content-addressed
+   - <!-- .element: class="fragment" -->
+     No timestamps, no uid/gid, no xattrs, only one way to represent the same tree
+- <!-- .element: class="fragment" -->
+  Automatic dedup of identical subtrees in different file system trees
+- <!-- .element: class="fragment" -->
+  Automatic dedup of identical blobs (and you can do more chunking underneath too)
+
+---
+
+## Storage model (cont.)
+- <!-- .element: class="fragment" -->
+  Granular seekable access into blobs
+- <!-- .element: class="fragment" -->
+  verified streaming thanks to BLAKE3 and Bao, faulty data is detected early on
+- <!-- .element: class="fragment" -->
+  Everything below can be retrieved from anyone without having to trust (P2P substitution, CDNs, โ€ฆ)
+
+---
+
+## Usecases
+- <!-- .element: class="fragment" -->
+  File system tree delivery
+- <!-- .element: class="fragment" -->
+  Container image delivery
+- <!-- .element: class="fragment" -->
+  Backing store for VCS
+- <!-- .element: class="fragment" -->
+  Granular access into large datasets
+
+---
+
+## Status
+- <!-- .element: class="fragment" -->
+  In-memory backend, a local K/V backend (Sled)
+- <!-- .element: class="fragment" -->
+  FUSE filesystem
+- <!-- .element: class="fragment" -->
+  A gRPC API to transfer things, bindings for golang and rust
+- <!-- .element: class="fragment" -->
+  some object storage backends in development (GCS, NATS)
+- <!-- .element: class="fragment" -->
+  FUTUREWORK: more storage backends / store composition / in-kernel module?
+
+Notes: of course you can use your own network protocol too, like HTTP CAS or iroh....plug different stores together to represent caches, blobfs
+
+---
+
+## Contributing
+
+- <!-- .element: class="fragment" -->
+  Join the IRC channel (`#tvl` on `hackint`), bridged to Matrix and XMPP
+- <!-- .element: class="fragment" -->
+  Check our issue tracker
+- <!-- .element: class="fragment" -->
+  Try to use it and tell us how you broke it!
+
+Note: if this sounds useful to you, reach out!
+
+---
+
+# Thanks!
+
+<style>
+.container{
+    display: flex;
+}
+.col{
+    flex: 1;
+}
+</style>
+
+<div class="container">
+
+<div class="col">
+Florian Klink / <a href="https://flokli.de">flokli.de</a><br />
+<img src="qrcode-flokli.svg" />
+</div>
+
+<div class="col">
+Tvix / <a href="https://tvix.dev">tvix.dev</a><br />
+<img src="qrcode-tvix.svg" />
+</div>
+
+</div>
+
+---
+
+## Structure
+
+[tvix-store graph](tvix-store-graph-blob-directory.svg)
diff --git a/users/flokli/presentations/2023-09-13-asg-tvix-store/tvix-store-graph-blob-directory.svg b/users/flokli/presentations/2023-09-13-asg-tvix-store/tvix-store-graph-blob-directory.svg
new file mode 100644
index 0000000000..2c87350d5b
--- /dev/null
+++ b/users/flokli/presentations/2023-09-13-asg-tvix-store/tvix-store-graph-blob-directory.svg
@@ -0,0 +1,17 @@
+<svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 691.01171875 836.19140625" width="691.01171875" height="836.19140625">
+  <!-- svg-source:excalidraw -->
+  
+  <defs>
+    <style class="style-fonts">
+      @font-face {
+        font-family: "Virgil";
+        src: url("https://excalidraw.com/Virgil.woff2");
+      }
+      @font-face {
+        font-family: "Cascadia";
+        src: url("https://excalidraw.com/Cascadia.woff2");
+      }
+    </style>
+    
+  </defs>
+  <rect x="0" y="0" width="691.01171875" height="836.19140625" fill="#ffffff"></rect><g stroke-linecap="round" transform="translate(10 64.05078125) rotate(0 71.10546875 23.685546875)"><path d="M3.15 2.74 C3.15 2.74, 3.15 2.74, 3.15 2.74 M3.15 2.74 C3.15 2.74, 3.15 2.74, 3.15 2.74 M1.58 10.65 C3.55 6.56, 7.08 5.83, 10.11 0.84 M1.58 10.65 C4.54 7.52, 5.83 5.33, 10.11 0.84 M1.97 16.29 C5.97 12.52, 7.97 9.89, 14.44 1.95 M1.97 16.29 C5.39 12.84, 8.96 8.01, 14.44 1.95 M1.71 22.69 C5.44 18.07, 11.5 12.49, 19.43 2.31 M1.71 22.69 C6.47 16.78, 11.69 12.31, 19.43 2.31 M1.45 29.09 C9.97 18.06, 21.19 8.77, 25.07 1.92 M1.45 29.09 C9.61 17.76, 19.35 7.56, 25.07 1.92 M1.85 34.73 C6.66 29.67, 14.83 20.48, 30.06 2.28 M1.85 34.73 C8.29 27.25, 17.05 18.03, 30.06 2.28 M2.24 40.37 C10.77 28.82, 20.92 18.16, 35.7 1.88 M2.24 40.37 C14.65 26.46, 25.75 13.13, 35.7 1.88 M3.29 45.26 C19.71 28.3, 32.79 9.79, 40.69 2.24 M3.29 45.26 C14.19 32.96, 23.82 22.41, 40.69 2.24 M6.97 47.13 C21.76 30.34, 38.17 12.66, 45.67 2.6 M6.97 47.13 C19.37 32.8, 32.58 18.66, 45.67 2.6 M12.61 46.74 C23.7 34.53, 32.71 23.85, 51.32 2.21 M12.61 46.74 C23.04 33.52, 34.64 21.81, 51.32 2.21 M17.6 47.1 C32.92 32.2, 44.82 17.61, 56.3 2.57 M17.6 47.1 C26.86 36.54, 34.43 27.02, 56.3 2.57 M22.58 47.46 C36.55 31.33, 51.38 11.55, 61.95 2.17 M22.58 47.46 C35.33 33.95, 46.66 20.15, 61.95 2.17 M28.23 47.06 C40.8 33.63, 55.34 16.98, 66.93 2.53 M28.23 47.06 C36.54 38.37, 43.25 29.02, 66.93 2.53 M33.21 47.42 C48.21 29.57, 61.74 14.8, 72.58 2.14 M33.21 47.42 C44.03 35.16, 55.8 21.76, 72.58 2.14 M38.86 47.03 C49.09 34.45, 58.15 26.51, 77.56 2.5 M38.86 47.03 C53.73 30.24, 67.18 15.29, 77.56 2.5 M43.84 47.39 C57.75 33, 73.31 14.12, 82.55 2.86 M43.84 47.39 C53.3 37.24, 61.17 27.55, 82.55 2.86 M49.49 46.99 C62.68 35.47, 70.45 21.6, 88.19 2.46 M49.49 46.99 C61.84 34.86, 73.11 21.46, 88.19 2.46 M54.47 47.35 C61.1 38.29, 69.25 27.83, 93.18 2.82 M54.47 47.35 C66.26 32.61, 76.99 20.02, 93.18 2.82 M60.12 46.96 C73.27 30.81, 84.64 16.32, 98.82 2.43 M60.12 46.96 C69.94 34.83, 80.66 21.92, 98.82 2.43 M65.1 47.32 C73.48 35.83, 86.65 26.47, 103.81 2.79 M65.1 47.32 C78.64 32.14, 91.97 18.02, 103.81 2.79 M70.09 47.68 C85.6 30.67, 98.3 12.05, 109.45 2.4 M70.09 47.68 C81.34 34.02, 94.32 21.42, 109.45 2.4 M75.73 47.28 C89.43 31.61, 102.84 18.22, 114.44 2.76 M75.73 47.28 C85.67 35.55, 96.35 22.65, 114.44 2.76 M80.72 47.64 C91.93 34.98, 103.63 17.52, 119.43 3.12 M80.72 47.64 C90.36 36.8, 98.88 24.6, 119.43 3.12 M86.36 47.25 C101.83 29.55, 117.32 12.7, 125.07 2.72 M86.36 47.25 C99.66 32.13, 111.08 17.18, 125.07 2.72 M91.35 47.61 C104.35 33.22, 117.8 19.95, 130.06 3.08 M91.35 47.61 C103.56 34.45, 114.2 20.27, 130.06 3.08 M96.99 47.21 C103.59 38.71, 113.94 28.96, 136.36 1.93 M96.99 47.21 C106.26 36.64, 117.24 24.44, 136.36 1.93 M101.98 47.57 C111.9 34.49, 126.09 20.88, 140.03 3.8 M101.98 47.57 C114.1 33.1, 128.09 16.65, 140.03 3.8 M106.97 47.93 C114.85 39.44, 121.62 28.8, 141.74 7.93 M106.97 47.93 C117.52 37.38, 125.94 26.56, 141.74 7.93 M112.61 47.54 C120.79 38.54, 126.18 28.87, 144.1 11.31 M112.61 47.54 C125.62 33.32, 137.05 19.84, 144.1 11.31 M117.6 47.9 C122.32 42.89, 130.44 32.85, 144.5 16.96 M117.6 47.9 C126.99 36.5, 135.5 27.33, 144.5 16.96 M123.24 47.51 C127.95 43.64, 132.17 36.33, 144.23 23.35 M123.24 47.51 C129.47 41.35, 136.09 32.69, 144.23 23.35 M128.23 47.87 C131.28 40.54, 137.28 36.59, 143.97 29.75 M128.23 47.87 C132.3 43.29, 136.94 37.23, 143.97 29.75 M132.56 48.98 C136.69 46.03, 136.69 42.77, 143.71 36.15 M132.56 48.98 C136.02 46.4, 138.73 42.29, 143.71 36.15" stroke="#a5d8ff" stroke-width="0.5" fill="none"></path><path d="M11.84 0 M11.84 0 C38.02 2.65, 61.84 0.28, 130.37 0 M11.84 0 C38.21 1.94, 65.76 1.5, 130.37 0 M130.37 0 C139.47 1, 143.75 5.88, 142.21 11.84 M130.37 0 C137.21 0.92, 139.99 4.87, 142.21 11.84 M142.21 11.84 C140.42 17.68, 141.72 25.2, 142.21 35.53 M142.21 11.84 C142.2 20.55, 141.47 28.9, 142.21 35.53 M142.21 35.53 C143.14 42.65, 138.92 45.76, 130.37 47.37 M142.21 35.53 C141.26 45.41, 136.07 48.58, 130.37 47.37 M130.37 47.37 C100.03 46.91, 70.33 46.17, 11.84 47.37 M130.37 47.37 C93.27 45.68, 57.65 45.47, 11.84 47.37 M11.84 47.37 C3.83 47.21, 0.04 43.58, 0 35.53 M11.84 47.37 C3.58 49.37, 2.09 44.62, 0 35.53 M0 35.53 C-0.11 25.56, 1.74 17.16, 0 11.84 M0 35.53 C-0.73 27.14, 0.25 16.41, 0 11.84 M0 11.84 C1.26 4.93, 5.18 0.97, 11.84 0 M0 11.84 C0.62 1.9, 3.42 2.05, 11.84 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(15 78.13632812499998) rotate(0 65.625 9.599999999999994)"><text x="0" y="0" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">0x01 0x02 0x03</text></g><g stroke-linecap="round" transform="translate(11.80078125 149.404296875) rotate(0 51.5 24.5)"><path d="M3.26 2.83 C3.26 2.83, 3.26 2.83, 3.26 2.83 M3.26 2.83 C3.26 2.83, 3.26 2.83, 3.26 2.83 M1.69 10.74 C4.79 9.22, 6.14 6.4, 10.22 0.93 M1.69 10.74 C4.82 7.44, 8.36 3.46, 10.22 0.93 M1.43 17.14 C5.08 10.94, 12.94 4.63, 14.55 2.05 M1.43 17.14 C7.42 12.34, 11.48 5.83, 14.55 2.05 M1.82 22.78 C7.79 17.76, 9.92 11.78, 20.19 1.65 M1.82 22.78 C6.91 17.54, 10.69 12.89, 20.19 1.65 M1.56 29.18 C10.68 19.58, 16.05 8.86, 25.18 2.01 M1.56 29.18 C6.66 24.1, 10.7 18.88, 25.18 2.01 M1.95 34.82 C14.06 20.7, 22.56 11.87, 30.16 2.37 M1.95 34.82 C12.05 23.95, 21.94 11.12, 30.16 2.37 M1.69 41.22 C7.98 31.97, 17.66 24.23, 35.81 1.98 M1.69 41.22 C14.67 26.87, 25.96 11.6, 35.81 1.98 M3.4 45.35 C12.42 31.5, 21.88 20.01, 40.79 2.34 M3.4 45.35 C14.79 32.81, 25.98 18.39, 40.79 2.34 M5.76 48.73 C19.03 33.54, 30.13 20.75, 46.44 1.94 M5.76 48.73 C15.95 35.19, 26.77 22.13, 46.44 1.94 M10.75 49.09 C25.08 35.85, 34.98 19.45, 51.42 2.3 M10.75 49.09 C25.36 34.29, 38.07 17.14, 51.42 2.3 M14.42 50.96 C25.24 34.64, 40.8 20.23, 57.07 1.91 M14.42 50.96 C31.56 31.52, 47.54 13.07, 57.07 1.91 M20.07 50.57 C36.58 31.78, 51.41 11.57, 62.05 2.27 M20.07 50.57 C35.27 32.37, 49.62 15.57, 62.05 2.27 M25.05 50.93 C33.69 40.39, 45.49 30.76, 67.7 1.87 M25.05 50.93 C39.57 35.58, 52.89 19.97, 67.7 1.87 M30.04 51.29 C42.51 36.46, 56.98 20.61, 72.68 2.23 M30.04 51.29 C39.08 40.34, 49.2 29.9, 72.68 2.23 M35.68 50.89 C47.44 39.9, 56.68 27.08, 78.33 1.84 M35.68 50.89 C52.05 33.69, 66.26 15.86, 78.33 1.84 M40.67 51.25 C53.32 36.02, 69.16 19.85, 83.31 2.2 M40.67 51.25 C53.19 37.93, 64.14 24.78, 83.31 2.2 M46.31 50.86 C62.11 32.31, 78.52 15.58, 88.96 1.8 M46.31 50.86 C57.68 37.06, 69.81 23.8, 88.96 1.8 M51.3 51.22 C59.29 38.98, 72.37 29.04, 93.94 2.16 M51.3 51.22 C64.73 35.51, 79.44 17.72, 93.94 2.16 M56.94 50.83 C69.81 34.76, 86.63 19.59, 98.93 2.52 M56.94 50.83 C72.69 32.66, 90.36 14.37, 98.93 2.52 M61.93 51.19 C77.07 35.49, 89.58 18.3, 101.95 5.15 M61.93 51.19 C76.05 33.49, 92.37 16.74, 101.95 5.15 M67.57 50.79 C76.42 41.59, 82.64 33.63, 103 10.04 M67.57 50.79 C76.25 40.25, 84.99 30.22, 103 10.04 M72.56 51.15 C79.09 44.86, 86.76 36.01, 103.4 15.68 M72.56 51.15 C78.4 43.41, 84.37 36.12, 103.4 15.68 M78.2 50.76 C84.15 41.95, 92.56 33.07, 103.13 22.08 M78.2 50.76 C88.29 39.34, 96.97 28.67, 103.13 22.08 M83.19 51.12 C88.53 45.31, 96.1 36.68, 103.53 27.72 M83.19 51.12 C91.24 41.6, 98.32 33.94, 103.53 27.72 M88.83 50.72 C90.5 47.29, 97.87 43.07, 103.27 34.12 M88.83 50.72 C94.68 44.06, 98.18 39.73, 103.27 34.12 M93.82 51.08 C96.56 47.12, 100.38 44.41, 104.97 38.25 M93.82 51.08 C96.61 47.17, 101.29 42.48, 104.97 38.25" stroke="#a5d8ff" stroke-width="0.5" fill="none"></path><path d="M12.25 0 M12.25 0 C28.51 0, 42.5 2.25, 90.75 0 M12.25 0 C32.14 -0.62, 52.39 1.04, 90.75 0 M90.75 0 C100.71 0.09, 101.8 3.77, 103 12.25 M90.75 0 C97.64 -0.83, 104 5.94, 103 12.25 M103 12.25 C101.33 19.82, 101.63 32.15, 103 36.75 M103 12.25 C102.35 21.13, 102.15 31.06, 103 36.75 M103 36.75 C104.56 46.87, 99.61 50.21, 90.75 49 M103 36.75 C104.15 46.69, 101.13 47.94, 90.75 49 M90.75 49 C62.94 48.31, 35.39 47.65, 12.25 49 M90.75 49 C67.73 49.76, 43.51 49.69, 12.25 49 M12.25 49 C3.6 47.48, -1.71 45.84, 0 36.75 M12.25 49 C3.19 49.75, -1.85 43.96, 0 36.75 M0 36.75 C-0.6 25.33, 0.5 18.11, 0 12.25 M0 36.75 C-0.07 30.3, -1.01 22.9, 0 12.25 M0 12.25 C-1.34 4.43, 2.32 -0.12, 12.25 0 M0 12.25 C-0.18 4.12, 4.26 -0.37, 12.25 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(16.80078125 164.30429687499998) rotate(0 42.1875 9.599999999999994)"><text x="0" y="0" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">0x04 0x05</text></g><g stroke-linecap="round" transform="translate(14.93359375 236.67578125) rotate(0 4.138671875 25.552734375)"><path d="M-0.29 2.4 C-0.29 2.4, -0.29 2.4, -0.29 2.4 M-0.29 2.4 C-0.29 2.4, -0.29 2.4, -0.29 2.4 M0.11 8.04 C1.82 6.13, 2.96 4.61, 4.7 2.76 M0.11 8.04 C1.05 6.92, 2.71 5.33, 4.7 2.76 M-0.15 14.44 C2.61 10.36, 4.52 9.27, 9.69 3.12 M-0.15 14.44 C3 10.45, 7.79 5.81, 9.69 3.12 M0.24 20.08 C1.78 17.33, 6.21 13.86, 9.43 9.52 M0.24 20.08 C3.9 16.52, 6.5 12.39, 9.43 9.52 M-0.02 26.48 C2.46 23.23, 7.19 20.21, 9.82 15.16 M-0.02 26.48 C2.52 23.58, 5.92 19.96, 9.82 15.16 M-0.28 32.88 C3.43 29.78, 4.96 26.51, 9.56 21.56 M-0.28 32.88 C1.68 30.37, 4.75 26.47, 9.56 21.56 M0.11 38.52 C2.43 35.55, 6.84 32.06, 9.3 27.96 M0.11 38.52 C3.34 34.69, 7.79 30.27, 9.3 27.96 M-0.15 44.92 C4.41 39.72, 7.29 36.44, 9.69 33.6 M-0.15 44.92 C2.92 41.4, 5.54 37.99, 9.69 33.6 M0.9 49.81 C2.58 47.91, 5.61 43.76, 9.43 40 M0.9 49.81 C3.14 46.67, 5.91 43.78, 9.43 40 M3.92 52.43 C5.01 50.49, 6.18 49.02, 9.82 45.64 M3.92 52.43 C5.89 50.77, 7.52 48.28, 9.82 45.64" stroke="#a5d8ff" stroke-width="0.5" fill="none"></path><path d="M2.07 0 M2.07 0 C2.97 -0.21, 4.11 0.12, 6.21 0 M2.07 0 C3.19 -0.16, 4.23 0.11, 6.21 0 M6.21 0 C8.97 0.45, 9.01 1.39, 8.28 2.07 M6.21 0 C9.57 -1.98, 9.89 2.6, 8.28 2.07 M8.28 2.07 C8.66 12.58, 9.73 22.31, 8.28 49.04 M8.28 2.07 C8.53 15.26, 7.49 28.86, 8.28 49.04 M8.28 49.04 C6.95 49.38, 7.58 52.75, 6.21 51.11 M8.28 49.04 C10.35 52.7, 9.64 53.24, 6.21 51.11 M6.21 51.11 C5.12 51.08, 3.76 50.9, 2.07 51.11 M6.21 51.11 C5.14 51.07, 4.25 51.01, 2.07 51.11 M2.07 51.11 C2.45 52.5, -1.17 52.21, 0 49.04 M2.07 51.11 C-1.47 50.3, 2.23 51.76, 0 49.04 M0 49.04 C-0.13 38.39, -0.48 26.48, 0 2.07 M0 49.04 C-1.31 32.15, -0.35 13.45, 0 2.07 M0 2.07 C0.41 0.36, -0.67 -0.56, 2.07 0 M0 2.07 C-0.35 -0.58, -1.03 -1.54, 2.07 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(10.23046875 10) rotate(0 23.4375 9.599999999999994)"><text x="0" y="0" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#1971c2" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">Blobs</text></g><g transform="translate(279.12890625 12.759374999999977) rotate(0 51.5625 9.599999999999994)"><text x="0" y="0" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#f08c00" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">Directories</text></g><g stroke-linecap="round" transform="translate(283.875 68.3828125) rotate(0 197 72.5)"><path d="M7.86 8.16 C7.86 8.16, 7.86 8.16, 7.86 8.16 M7.86 8.16 C7.86 8.16, 7.86 8.16, 7.86 8.16 M3.66 19.09 C8.28 14.49, 8.49 11.04, 18.1 2.48 M3.66 19.09 C7.7 14.57, 11.52 11.66, 18.1 2.48 M2.75 26.24 C11.51 19.05, 17.92 11.98, 24.4 1.33 M2.75 26.24 C7.23 20.55, 12.55 15.89, 24.4 1.33 M2.48 32.64 C12.12 24.05, 19.28 13.72, 30.7 0.18 M2.48 32.64 C9.16 24.56, 14.83 17.39, 30.7 0.18 M2.88 38.28 C11.43 28.28, 22.31 16.44, 34.37 2.05 M2.88 38.28 C14.6 24.25, 27.89 9.94, 34.37 2.05 M2.62 44.68 C9.32 34.89, 17.39 26.88, 39.36 2.41 M2.62 44.68 C11.95 33.82, 21.66 21.02, 39.36 2.41 M2.36 51.07 C12.81 41.21, 19.43 27.76, 45 2.02 M2.36 51.07 C16.68 33.29, 30.46 16.46, 45 2.02 M2.75 56.72 C16.64 41.98, 28.98 26.21, 49.99 2.38 M2.75 56.72 C21 35.95, 39.52 14.81, 49.99 2.38 M2.49 63.11 C19.63 44.26, 39.08 21.21, 55.63 1.98 M2.49 63.11 C17.01 46.73, 31.61 30.35, 55.63 1.98 M2.88 68.76 C22.9 46.33, 43.08 21.26, 60.62 2.34 M2.88 68.76 C23.19 45.39, 41.88 24.57, 60.62 2.34 M2.62 75.16 C23.83 54.32, 39.89 29.17, 66.26 1.95 M2.62 75.16 C18.47 56.13, 34.46 38.2, 66.26 1.95 M2.36 81.55 C29.87 50.88, 54.51 22.49, 71.25 2.31 M2.36 81.55 C26.5 54.38, 49.03 26.57, 71.25 2.31 M2.76 87.2 C25.76 61.24, 52.69 29.56, 76.89 1.91 M2.76 87.2 C22.68 64.01, 42.85 40.22, 76.89 1.91 M2.49 93.59 C30.49 61.54, 57.87 26.06, 81.88 2.27 M2.49 93.59 C31.33 60.52, 59.32 27.03, 81.88 2.27 M2.89 99.24 C28.16 70.88, 49.29 44.38, 87.52 1.88 M2.89 99.24 C22.19 77.54, 40.48 55.68, 87.52 1.88 M2.63 105.64 C27.42 78.52, 50.89 50.01, 92.51 2.24 M2.63 105.64 C38.6 64.43, 74.48 23.22, 92.51 2.24 M2.37 112.03 C35.69 71.87, 67.49 34.66, 98.15 1.85 M2.37 112.03 C29.87 80.37, 57.31 48.8, 98.15 1.85 M2.76 117.68 C35.35 78.95, 70.62 41.89, 103.14 2.21 M2.76 117.68 C24.16 94.43, 44.43 69.26, 103.14 2.21 M3.16 123.32 C27.31 95.67, 53.64 64.97, 108.78 1.81 M3.16 123.32 C40.3 79.27, 78.36 37.25, 108.78 1.81 M4.86 127.45 C35.22 90.31, 66.42 52.47, 113.77 2.17 M4.86 127.45 C27.89 100.07, 53.15 74.47, 113.77 2.17 M5.26 133.1 C41.54 89.35, 82.58 48.1, 119.41 1.78 M5.26 133.1 C50.78 80.9, 96.57 28.36, 119.41 1.78 M7.62 136.47 C50.54 85.68, 96.45 32.28, 124.4 2.14 M7.62 136.47 C50.54 87.26, 95.13 37.29, 124.4 2.14 M10.64 139.1 C44.47 96.28, 83.89 53.02, 130.04 1.74 M10.64 139.1 C49.89 96.71, 86.98 53.36, 130.04 1.74 M14.31 140.97 C55.74 92.08, 97.64 42.48, 135.03 2.1 M14.31 140.97 C49.36 97.94, 86.03 55.71, 135.03 2.1 M17.33 143.59 C62.88 90.12, 107.49 38.88, 140.67 1.71 M17.33 143.59 C52.29 102.09, 87.68 60.93, 140.67 1.71 M21.01 145.46 C48.28 114.13, 76.95 81.48, 145.66 2.07 M21.01 145.46 C64.61 95.84, 107.71 47.87, 145.66 2.07 M25.34 146.58 C73.23 89.86, 125.31 35.24, 150.65 2.43 M25.34 146.58 C51.9 117.94, 78.32 86.29, 150.65 2.43 M32.29 144.67 C78.07 95.21, 118.24 44.56, 156.29 2.03 M32.29 144.67 C74.54 98.13, 114.59 49.94, 156.29 2.03 M37.28 145.03 C85.09 88.58, 133.69 36.74, 161.28 2.39 M37.28 145.03 C69.44 108.12, 100.26 71.2, 161.28 2.39 M42.92 144.64 C90.72 91.41, 136.95 40.49, 166.92 2 M42.92 144.64 C66.95 114.58, 93.7 85.42, 166.92 2 M47.91 145 C75.02 114.35, 102.25 80.22, 171.91 2.36 M47.91 145 C87.4 99.39, 126.35 54.03, 171.91 2.36 M52.9 145.36 C79.04 113.6, 107.32 79.81, 177.55 1.96 M52.9 145.36 C77.61 115.94, 103.48 86, 177.55 1.96 M58.54 144.96 C92.12 105.3, 128.8 63.9, 182.54 2.32 M58.54 144.96 C106.61 90.51, 152.9 36.35, 182.54 2.32 M63.53 145.32 C107.47 99.27, 149.03 48.65, 188.18 1.93 M63.53 145.32 C96.13 109.61, 126.87 72.99, 188.18 1.93 M69.17 144.93 C102.47 104.11, 137.86 61.81, 193.17 2.29 M69.17 144.93 C110.04 98.38, 152.23 51.84, 193.17 2.29 M74.16 145.29 C104.78 108.19, 138.63 71.58, 198.81 1.9 M74.16 145.29 C99.31 117.03, 123.84 87.71, 198.81 1.9 M79.8 144.9 C130.71 87.32, 178.54 30.53, 203.8 2.26 M79.8 144.9 C124.29 94.42, 167.51 43.91, 203.8 2.26 M84.79 145.26 C111.41 111.48, 143.37 80.45, 209.44 1.86 M84.79 145.26 C130.36 93.05, 176.97 39.33, 209.44 1.86 M90.43 144.86 C128.08 97.62, 168.49 56.08, 214.43 2.22 M90.43 144.86 C130.35 96.04, 171.08 48.72, 214.43 2.22 M95.42 145.22 C146.19 90.39, 192.4 31.56, 220.07 1.83 M95.42 145.22 C120.72 114.85, 146.9 84.4, 220.07 1.83 M101.06 144.83 C127.22 111.52, 152.8 83.04, 225.06 2.19 M101.06 144.83 C132.86 108.13, 163.34 71.68, 225.06 2.19 M106.05 145.19 C154.17 92.56, 198.8 39.31, 230.7 1.79 M106.05 145.19 C136.53 110.59, 166.08 76.75, 230.7 1.79 M111.69 144.79 C138.65 117.15, 162.06 85.81, 235.69 2.15 M111.69 144.79 C137.61 113.65, 164.22 81.98, 235.69 2.15 M116.68 145.15 C157.06 99.25, 194.58 56.02, 241.33 1.76 M116.68 145.15 C155.1 99.61, 194.87 53.74, 241.33 1.76 M122.32 144.76 C155.38 104.12, 188.9 66.56, 246.32 2.12 M122.32 144.76 C171.19 89.94, 218.89 34.94, 246.32 2.12 M127.31 145.12 C160.63 107.75, 195.41 68.39, 251.96 1.72 M127.31 145.12 C171.79 94.11, 216.13 43.25, 251.96 1.72 M132.95 144.72 C167.14 104.52, 206.3 64.21, 256.95 2.08 M132.95 144.72 C164.99 105.52, 198.48 68.92, 256.95 2.08 M137.94 145.08 C183.71 92.31, 226.45 45.7, 262.59 1.69 M137.94 145.08 C163.89 115.91, 190.22 85.14, 262.59 1.69 M143.58 144.69 C174.92 109.48, 206.89 70.46, 267.58 2.05 M143.58 144.69 C170.4 113.21, 199.74 82.17, 267.58 2.05 M148.57 145.05 C178.08 111.76, 203.33 81.73, 272.56 2.41 M148.57 145.05 C173.64 115.53, 198.59 86.39, 272.56 2.41 M154.21 144.65 C187.55 108.07, 219.55 68.66, 278.21 2.01 M154.21 144.65 C200.06 91.97, 243.47 39.89, 278.21 2.01 M159.2 145.01 C186.66 112.8, 212.53 85.21, 283.19 2.37 M159.2 145.01 C207.12 89.92, 255.48 37.14, 283.19 2.37 M164.19 145.37 C202.12 104.92, 237.2 59.79, 288.84 1.98 M164.19 145.37 C194.82 106.77, 226.84 70.45, 288.84 1.98 M169.83 144.98 C212.12 93.05, 258.35 43.46, 293.82 2.34 M169.83 144.98 C201.44 106.46, 235.81 67.61, 293.82 2.34 M174.82 145.34 C207.54 111.65, 235.58 74.76, 299.47 1.94 M174.82 145.34 C199.92 118.44, 225.68 88.79, 299.47 1.94 M180.46 144.94 C208.5 116.42, 230.68 85.13, 304.45 2.3 M180.46 144.94 C218.97 100.78, 259.51 55.75, 304.45 2.3 M185.45 145.31 C221.24 102.72, 256.86 59.74, 310.1 1.91 M185.45 145.31 C232.8 92.21, 280.33 37.72, 310.1 1.91 M191.09 144.91 C238.27 88.19, 288.2 35.33, 315.08 2.27 M191.09 144.91 C220.59 109.55, 252.22 74.41, 315.08 2.27 M196.08 145.27 C239.76 94.85, 284.75 46.35, 320.73 1.88 M196.08 145.27 C230.49 104.94, 265.25 68.33, 320.73 1.88 M201.72 144.88 C234.66 108.79, 263.22 71.79, 325.71 2.24 M201.72 144.88 C237.32 101.42, 273.7 59.07, 325.71 2.24 M206.71 145.24 C253.21 90.64, 300.99 37.76, 331.36 1.84 M206.71 145.24 C240.65 108.22, 273.71 69.33, 331.36 1.84 M212.35 144.84 C242.19 114.5, 267.67 79.17, 336.34 2.2 M212.35 144.84 C236.6 115.07, 262.16 86.57, 336.34 2.2 M217.34 145.2 C251.25 105.29, 286.42 64.08, 341.99 1.81 M217.34 145.2 C249.04 108.05, 278.75 71.6, 341.99 1.81 M222.98 144.81 C254.78 109.71, 284.95 73.51, 346.97 2.17 M222.98 144.81 C251.44 112.17, 277.38 81.44, 346.97 2.17 M227.97 145.17 C258.67 109.25, 290.39 71.64, 352.62 1.77 M227.97 145.17 C253.46 115.85, 279.91 85.09, 352.62 1.77 M233.61 144.77 C279.92 90.22, 328.36 36.09, 357.6 2.13 M233.61 144.77 C263.54 109.84, 296.58 72.44, 357.6 2.13 M238.6 145.13 C284.33 94.02, 329.86 38.18, 363.25 1.74 M238.6 145.13 C270.6 110.97, 300.1 77.3, 363.25 1.74 M244.24 144.74 C285.03 98.47, 325.99 49.52, 368.23 2.1 M244.24 144.74 C288.81 94.28, 333.65 43.13, 368.23 2.1 M249.23 145.1 C274.07 113.24, 304.32 83.42, 373.22 2.46 M249.23 145.1 C288.32 102.57, 326.71 58.15, 373.22 2.46 M254.87 144.7 C297.9 94.29, 343.78 43.11, 377.55 3.57 M254.87 144.7 C286.66 106.11, 320.39 66.72, 377.55 3.57 M259.86 145.06 C295.8 103.74, 331.06 61.72, 381.23 5.44 M259.86 145.06 C298.82 101.67, 336.82 56.01, 381.23 5.44 M265.5 144.67 C313.19 90.52, 361.44 33.85, 384.9 7.31 M265.5 144.67 C292.92 114.87, 319.46 83.51, 384.9 7.31 M270.49 145.03 C293.76 118.67, 317.19 90.91, 387.92 9.94 M270.49 145.03 C298.6 112.33, 327.42 78.58, 387.92 9.94 M276.13 144.63 C309.18 107.62, 339.56 70.71, 390.28 13.32 M276.13 144.63 C314.84 98.86, 353.06 54.55, 390.28 13.32 M281.12 144.99 C305.94 116.7, 328.37 88.76, 391.99 17.45 M281.12 144.99 C321.03 97.29, 362.22 51.33, 391.99 17.45 M286.1 145.35 C324.81 103.13, 365.35 57.02, 393.04 22.34 M286.1 145.35 C313.97 112.26, 343.79 77.31, 393.04 22.34 M291.75 144.96 C324.16 107.97, 358.78 71.44, 395.4 25.72 M291.75 144.96 C322.28 110.33, 352.3 74.13, 395.4 25.72 M296.73 145.32 C322.53 113.82, 350.61 86.17, 395.14 32.11 M296.73 145.32 C330.9 104.68, 367.32 64.68, 395.14 32.11 M302.38 144.93 C320.38 122.95, 340.8 101.95, 395.54 37.76 M302.38 144.93 C328.33 112.21, 356.43 82.25, 395.54 37.76 M307.36 145.29 C337.92 110.64, 366.55 76.31, 395.28 44.15 M307.36 145.29 C325.37 123.43, 344.42 100.44, 395.28 44.15 M313.01 144.89 C335.45 120.95, 355.71 93.18, 395.01 50.55 M313.01 144.89 C342.22 110.33, 372.53 76.59, 395.01 50.55 M317.99 145.25 C335.46 125.76, 350.14 107.98, 395.41 56.2 M317.99 145.25 C334.45 126.54, 349.07 108.69, 395.41 56.2 M323.64 144.86 C350.88 112.04, 380.82 81.41, 395.15 62.59 M323.64 144.86 C349.73 114.44, 376.12 84.65, 395.15 62.59 M328.62 145.22 C342.07 126.15, 358.11 110, 395.54 68.24 M328.62 145.22 C352.38 118.21, 373.64 91.69, 395.54 68.24 M334.27 144.82 C356.58 116.53, 380.06 91.2, 395.28 74.63 M334.27 144.82 C352.85 123.85, 373.24 101.61, 395.28 74.63 M339.25 145.18 C350.58 130.51, 366.65 115.79, 395.67 80.28 M339.25 145.18 C360.84 120.94, 381.57 97.17, 395.67 80.28 M344.9 144.79 C359.95 129.93, 374.97 110.51, 395.41 86.68 M344.9 144.79 C359.66 128.95, 373.84 112.99, 395.41 86.68 M349.88 145.15 C365.74 124.35, 384.39 105.92, 395.81 92.32 M349.88 145.15 C365.01 128.41, 378.61 111.44, 395.81 92.32 M355.53 144.75 C366.83 128.33, 381.85 115.58, 395.55 98.72 M355.53 144.75 C366.51 134.09, 376.57 121.06, 395.55 98.72 M360.51 145.11 C371.57 133.46, 379.69 124.34, 395.94 104.36 M360.51 145.11 C371.82 131.23, 384.33 117.41, 395.94 104.36 M364.19 146.98 C372.36 139.23, 375.92 131.75, 395.68 110.76 M364.19 146.98 C375.25 134.47, 383.71 124.14, 395.68 110.76 M370.49 145.83 C377.43 139.68, 383.18 129.21, 396.73 115.65 M370.49 145.83 C377.33 137.24, 382.58 130.01, 396.73 115.65 M377.44 143.93 C380.98 139.22, 385.26 134.38, 391.88 127.33 M377.44 143.93 C381.01 139.93, 385.77 134.2, 391.88 127.33" stroke="#ffec99" stroke-width="0.5" fill="none"></path><path d="M32 0 M32 0 C158.41 -0.65, 283.84 1.37, 362 0 M32 0 C99.31 -2.94, 165.01 -1.66, 362 0 M362 0 C382.37 -1.19, 392.06 11.27, 394 32 M362 0 C383.53 0.55, 395.84 12.5, 394 32 M394 32 C396.17 49.29, 394.95 72.1, 394 113 M394 32 C394.74 57.51, 393.79 80.56, 394 113 M394 113 C393.49 135.83, 381.87 143.9, 362 145 M394 113 C391.98 135.04, 383.6 143.19, 362 145 M362 145 C291.31 144.27, 221.15 142.78, 32 145 M362 145 C249.93 143.31, 138.97 142.96, 32 145 M32 145 C8.71 146.69, -1.86 135.95, 0 113 M32 145 C10.21 143.8, 1.65 132.18, 0 113 M0 113 C0.55 94.72, -1.35 75.26, 0 32 M0 113 C-0.88 90.36, -1.5 66.27, 0 32 M0 32 C1.9 12.2, 9.12 -1.48, 32 0 M0 32 C0.22 11.69, 10.1 1.96, 32 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(288.875 73.3828125) rotate(0 140.625 67.19999999999999)"><text x="0" y="0" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">directories: []</text><text x="0" y="19.2" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">files:</text><text x="0" y="38.4" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge"> - name: .keep</text><text x="0" y="57.599999999999994" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">   digest: &lt;empty-blob-digest&gt;</text><text x="0" y="76.8" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">   size: 0</text><text x="0" y="96" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">   executable: false</text><text x="0" y="115.19999999999999" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">symlinks: []</text></g><g stroke-linecap="round" transform="translate(279.59193843887 271.159696266393) rotate(0 198.99999999999994 130)"><path d="M7.86 8.16 C7.86 8.16, 7.86 8.16, 7.86 8.16 M7.86 8.16 C7.86 8.16, 7.86 8.16, 7.86 8.16 M3.66 19.09 C7.06 13.81, 9.24 9.34, 18.1 2.48 M3.66 19.09 C8.44 11.73, 13.76 5.27, 18.1 2.48 M2.75 26.24 C10 16.18, 18.61 6.13, 24.4 1.33 M2.75 26.24 C9.44 19.31, 14.57 12.4, 24.4 1.33 M2.48 32.64 C10.34 21.03, 17.74 12.3, 30.7 0.18 M2.48 32.64 C13.49 21.44, 21.83 9.48, 30.7 0.18 M2.88 38.28 C12.91 25, 26.86 13.76, 34.37 2.05 M2.88 38.28 C12.64 28.15, 21.63 16.63, 34.37 2.05 M2.62 44.68 C14.17 33.27, 22.89 18.23, 39.36 2.41 M2.62 44.68 C12.44 34.48, 21.24 23.55, 39.36 2.41 M2.36 51.07 C18.27 36.19, 31.27 19.8, 45 2.02 M2.36 51.07 C15.34 37.29, 27.12 23.41, 45 2.02 M2.75 56.72 C18.22 37.56, 35.93 17.94, 49.99 2.38 M2.75 56.72 C20.57 34.56, 37.8 14.24, 49.99 2.38 M2.49 63.11 C18.51 41.78, 36.66 23.69, 55.63 1.98 M2.49 63.11 C16.06 46.84, 30.13 32.58, 55.63 1.98 M2.23 69.51 C14.08 54.4, 27.81 42.3, 60.62 2.34 M2.23 69.51 C14.8 54.54, 28.11 38.47, 60.62 2.34 M2.62 75.16 C23.22 53.14, 45.11 28.68, 66.26 1.95 M2.62 75.16 C18.68 55.49, 35.76 36.87, 66.26 1.95 M2.36 81.55 C29.49 51.06, 52.67 20.07, 71.25 2.31 M2.36 81.55 C23.35 59.17, 43.86 35.46, 71.25 2.31 M2.76 87.2 C29.08 59.14, 54.26 30.38, 76.89 1.91 M2.76 87.2 C25.2 61.93, 47.39 35.45, 76.89 1.91 M2.49 93.59 C22.97 73.16, 41.24 50.49, 81.88 2.27 M2.49 93.59 C22.85 70.13, 41.2 48.51, 81.88 2.27 M2.23 99.99 C26.73 70.85, 53 42.34, 87.52 1.88 M2.23 99.99 C26.11 70.65, 52.18 43.37, 87.52 1.88 M2.63 105.64 C29.25 73.43, 57.28 44.11, 92.51 2.24 M2.63 105.64 C22.21 84.02, 40.09 61.9, 92.51 2.24 M2.37 112.03 C24.78 84.92, 46.32 60.61, 98.15 1.85 M2.37 112.03 C40.71 68.73, 77.43 25.35, 98.15 1.85 M2.1 118.43 C32.45 81.76, 64.34 45.79, 103.14 2.21 M2.1 118.43 C26.04 90.13, 50.16 63.23, 103.14 2.21 M2.5 124.07 C34.35 88.85, 66.58 53.31, 108.78 1.81 M2.5 124.07 C35.8 86.3, 67.38 48.42, 108.78 1.81 M2.24 130.47 C44.13 79.64, 85.07 32.68, 113.77 2.17 M2.24 130.47 C28.26 100.65, 54.41 68.41, 113.77 2.17 M2.63 136.11 C39.83 96.08, 73.86 52.5, 119.41 1.78 M2.63 136.11 C27.92 105.97, 55.2 76.2, 119.41 1.78 M2.37 142.51 C38.64 103.62, 72.54 62.27, 124.4 2.14 M2.37 142.51 C38.27 104.7, 71.42 64.68, 124.4 2.14 M2.11 148.91 C31.62 115.14, 60.21 82.46, 130.04 1.74 M2.11 148.91 C30.64 115.22, 60.37 82.49, 130.04 1.74 M2.5 154.55 C45.43 105.72, 87.93 57.79, 135.03 2.1 M2.5 154.55 C39.77 111.59, 78.21 68.92, 135.03 2.1 M2.24 160.95 C40.01 120.72, 75.93 77.72, 140.67 1.71 M2.24 160.95 C44.66 112.93, 87.03 64.56, 140.67 1.71 M1.98 167.35 C39.14 124.53, 74.75 81.57, 145.66 2.07 M1.98 167.35 C47.85 117.91, 91.41 66.21, 145.66 2.07 M2.38 172.99 C58.38 106.95, 115.31 43.43, 150.65 2.43 M2.38 172.99 C53.11 115.34, 104.81 56.32, 150.65 2.43 M2.11 179.39 C64.27 108.42, 124.58 39.52, 156.29 2.03 M2.11 179.39 C42.26 131.15, 83.61 83.43, 156.29 2.03 M2.51 185.03 C62.42 116.85, 122.68 43.77, 161.28 2.39 M2.51 185.03 C56.66 122.43, 112.32 58.31, 161.28 2.39 M2.25 191.43 C51.58 133.03, 102.03 75.72, 166.92 2 M2.25 191.43 C38.3 149.45, 75.45 107.32, 166.92 2 M1.99 197.83 C42.68 152.88, 80.66 108.12, 171.91 2.36 M1.99 197.83 C66.15 123.85, 132.83 47.33, 171.91 2.36 M2.38 203.47 C55.91 143.98, 107.7 83.76, 177.55 1.96 M2.38 203.47 C56.81 142.73, 110.46 81.58, 177.55 1.96 M2.12 209.87 C66.55 137, 131.82 62.14, 182.54 2.32 M2.12 209.87 C50.61 157.03, 98.73 101.44, 182.54 2.32 M1.86 216.27 C63.05 142.96, 126.25 69.69, 188.18 1.93 M1.86 216.27 C65.44 140.95, 131.03 66.87, 188.18 1.93 M2.25 221.91 C59.05 161.57, 111.38 97.22, 193.17 2.29 M2.25 221.91 C52.49 164.23, 101.5 106.36, 193.17 2.29 M1.33 229.06 C64.26 156.57, 124.34 84.79, 198.81 1.9 M1.33 229.06 C60.97 162.1, 120.98 94.62, 198.81 1.9 M1.73 234.71 C54.24 174.7, 106.54 116.97, 203.8 2.26 M1.73 234.71 C76.5 149.79, 152.59 63.83, 203.8 2.26 M2.78 239.59 C49.77 186.82, 94.95 134.94, 209.44 1.86 M2.78 239.59 C83.35 146.44, 163.63 53.44, 209.44 1.86 M4.49 243.73 C48.55 190.47, 93.86 139.53, 214.43 2.22 M4.49 243.73 C63.53 174.61, 122.23 106.2, 214.43 2.22 M6.19 247.86 C77.48 165.73, 149.04 81.52, 220.07 1.83 M6.19 247.86 C85.41 157.55, 164.08 66.86, 220.07 1.83 M8.56 251.24 C90.22 155.68, 175.16 56.51, 225.06 2.19 M8.56 251.24 C89.12 157.29, 172.43 62.13, 225.06 2.19 M10.92 254.62 C69.41 191.54, 124.13 126, 230.7 1.79 M10.92 254.62 C69.41 187.27, 127.03 120.42, 230.7 1.79 M13.94 257.24 C82.45 176.73, 153.61 97.39, 235.69 2.15 M13.94 257.24 C93.45 166.82, 173.91 75.69, 235.69 2.15 M18.27 258.36 C76.98 194.1, 131.53 129.14, 241.33 1.76 M18.27 258.36 C66.67 201.61, 115.46 145.37, 241.33 1.76 M22.6 259.47 C75.95 201, 128.47 140.06, 246.32 2.12 M22.6 259.47 C70.35 207.82, 116.17 155.22, 246.32 2.12 M26.27 261.34 C95.93 180.59, 169.95 95.39, 251.96 1.72 M26.27 261.34 C89.37 190.53, 152.1 118.22, 251.96 1.72 M32.57 260.19 C96.06 189.84, 155.98 120.97, 256.95 2.08 M32.57 260.19 C78.18 205.29, 124.7 151.48, 256.95 2.08 M37.56 260.55 C117 171.22, 192.42 83.99, 262.59 1.69 M37.56 260.55 C121.96 163.6, 206.2 67.19, 262.59 1.69 M43.2 260.16 C105.28 189.09, 169.45 117.92, 267.58 2.05 M43.2 260.16 C123.84 168.01, 203.45 76.43, 267.58 2.05 M48.19 260.52 C117.81 178.99, 186.9 100.6, 272.56 2.41 M48.19 260.52 C125.53 171.84, 202.4 82.45, 272.56 2.41 M53.83 260.12 C138.08 162.28, 222.76 67.81, 278.21 2.01 M53.83 260.12 C128.55 171.85, 204.35 84.41, 278.21 2.01 M58.82 260.48 C127.06 184.42, 193 106.58, 283.19 2.37 M58.82 260.48 C135.91 173.81, 211.1 87.23, 283.19 2.37 M64.46 260.09 C109.71 206.88, 156.75 154.77, 288.84 1.98 M64.46 260.09 C142.84 169.53, 220.7 80.3, 288.84 1.98 M69.45 260.45 C138.09 183.73, 208 105.35, 293.82 2.34 M69.45 260.45 C156.6 162.77, 241.99 64.65, 293.82 2.34 M75.09 260.06 C143.01 182.6, 211.21 103.49, 299.47 1.94 M75.09 260.06 C136 192, 195.87 123.37, 299.47 1.94 M80.08 260.42 C126.17 206.79, 172.95 151.57, 304.45 2.3 M80.08 260.42 C129.25 204.69, 179.83 146.37, 304.45 2.3 M85.72 260.02 C157.76 178.28, 226.22 97.47, 310.1 1.91 M85.72 260.02 C149.05 184.68, 213.67 111.24, 310.1 1.91 M90.71 260.38 C181.48 157.9, 269.25 55.91, 315.08 2.27 M90.71 260.38 C157.59 183.34, 224.55 106.5, 315.08 2.27 M95.7 260.74 C174.5 168.64, 255.03 76.7, 320.73 1.88 M95.7 260.74 C164.52 183.36, 232.23 105.12, 320.73 1.88 M101.34 260.35 C181.6 167.21, 261.15 73.9, 325.71 2.24 M101.34 260.35 C177.77 172.29, 255.39 82.95, 325.71 2.24 M106.33 260.71 C168.93 189.98, 230.06 115.81, 331.36 1.84 M106.33 260.71 C170.53 184.03, 237.17 107.92, 331.36 1.84 M111.97 260.31 C176.35 190.63, 236.85 119.43, 336.34 2.2 M111.97 260.31 C157.57 208.21, 204.38 154.93, 336.34 2.2 M116.96 260.67 C164.3 205.68, 212.63 148.72, 341.99 1.81 M116.96 260.67 C197.94 169.46, 278.58 77.5, 341.99 1.81 M122.6 260.28 C175.27 200.79, 229.84 138.31, 346.97 2.17 M122.6 260.28 C172.5 202.83, 220.76 146.35, 346.97 2.17 M127.59 260.64 C215.15 158.17, 304.42 54.77, 352.62 1.77 M127.59 260.64 C188.96 189.82, 250.22 118.28, 352.62 1.77 M133.23 260.24 C183.32 201.93, 235.94 141.72, 357.6 2.13 M133.23 260.24 C184.28 203.09, 233.15 145.34, 357.6 2.13 M138.22 260.6 C220.44 165.6, 301.95 72.93, 363.25 1.74 M138.22 260.6 C215.16 172.58, 291.69 83.91, 363.25 1.74 M143.86 260.21 C216.13 172.96, 292.05 87.04, 368.23 2.1 M143.86 260.21 C223.54 169.38, 302.36 80.03, 368.23 2.1 M148.85 260.57 C228.87 169.81, 309.22 79.04, 374.53 0.95 M148.85 260.57 C225.85 169.3, 304.04 78.63, 374.53 0.95 M154.49 260.17 C230.89 172.65, 306.45 86.57, 378.21 2.82 M154.49 260.17 C210.94 195.8, 265.6 130.92, 378.21 2.82 M159.48 260.53 C223.94 184.22, 290.75 110.57, 382.54 3.93 M159.48 260.53 C244.05 163.85, 329.27 67.22, 382.54 3.93 M165.12 260.14 C236.95 176.83, 309.56 92.2, 386.87 5.05 M165.12 260.14 C221.23 195.95, 279.16 130.62, 386.87 5.05 M170.11 260.5 C221.21 201.49, 273.24 142.28, 389.23 8.43 M170.11 260.5 C218.37 205.87, 266.44 150.41, 389.23 8.43 M175.75 260.11 C241.58 187.82, 304.45 113.61, 392.25 11.05 M175.75 260.11 C222.69 207.98, 267.39 155.95, 392.25 11.05 M180.74 260.47 C245.33 188.58, 307.54 115.78, 395.27 13.68 M180.74 260.47 C261.6 168.2, 340.34 76.88, 395.27 13.68 M186.38 260.07 C251.57 186.65, 319.28 107.6, 396.32 18.56 M186.38 260.07 C236.65 200.09, 289.45 140.81, 396.32 18.56 M191.37 260.43 C255.8 183.29, 323.42 107.08, 397.37 23.45 M191.37 260.43 C233.86 209.7, 277.09 160.88, 397.37 23.45 M197.01 260.04 C273.9 173.76, 350.14 83.39, 397.77 29.09 M197.01 260.04 C238.49 210.28, 280.91 161.3, 397.77 29.09 M202 260.4 C249.95 204.58, 301.21 145.35, 399.47 33.23 M202 260.4 C246.27 207.11, 292.1 154.9, 399.47 33.23 M207.64 260 C282.04 175.37, 353.03 93.88, 399.21 39.63 M207.64 260 C252.23 206.89, 298.22 154.54, 399.21 39.63 M212.63 260.36 C270.79 190.87, 332.59 125.04, 399.61 45.27 M212.63 260.36 C255.76 212.2, 297.66 164.88, 399.61 45.27 M217.62 260.72 C270.87 200.01, 324.39 137.32, 399.34 51.67 M217.62 260.72 C286.2 182.97, 352.23 105.9, 399.34 51.67 M223.26 260.33 C266.88 209.47, 311.98 157.05, 399.74 57.31 M223.26 260.33 C270.48 203.33, 319.36 147.73, 399.74 57.31 M228.25 260.69 C264.48 217.85, 298.81 176.68, 399.48 63.71 M228.25 260.69 C287.06 192.75, 345.09 124.31, 399.48 63.71 M233.89 260.29 C283.82 202.81, 333.35 147.36, 399.22 70.11 M233.89 260.29 C296.32 189.36, 357.95 117.97, 399.22 70.11 M238.88 260.65 C288.24 201.51, 340.4 144.75, 399.61 75.75 M238.88 260.65 C302.98 187.65, 365.83 115.51, 399.61 75.75 M244.52 260.26 C301.41 192.57, 362.82 123.97, 399.35 82.15 M244.52 260.26 C276.87 221.94, 309.71 183.55, 399.35 82.15 M249.51 260.62 C294.86 210.74, 338.19 159.76, 399.74 87.79 M249.51 260.62 C288.68 213.68, 329.48 168.33, 399.74 87.79 M255.15 260.22 C306.36 203.12, 357.73 144.26, 399.48 94.19 M255.15 260.22 C310.1 197.2, 364.53 133.07, 399.48 94.19 M260.14 260.58 C306.6 208.36, 353.31 154.47, 399.22 100.59 M260.14 260.58 C314.84 198.11, 370.53 134.35, 399.22 100.59 M265.78 260.19 C310.07 209.18, 353.85 155.07, 399.62 106.23 M265.78 260.19 C313.52 203.42, 361.67 148.41, 399.62 106.23 M270.77 260.55 C312.94 210.71, 350.92 165.03, 399.35 112.63 M270.77 260.55 C313.74 212.09, 354.22 165.25, 399.35 112.63 M276.41 260.15 C322.47 205.94, 367.79 155.3, 399.75 118.27 M276.41 260.15 C319.24 210.97, 362.85 162.61, 399.75 118.27 M281.4 260.51 C309.82 228.04, 337.57 195, 399.49 124.67 M281.4 260.51 C315.37 218.59, 351.47 180.05, 399.49 124.67 M287.04 260.12 C329.88 210.1, 376.19 158.03, 399.23 131.06 M287.04 260.12 C314.45 229.24, 339.01 200.91, 399.23 131.06 M292.03 260.48 C332.66 211.87, 372.61 167.01, 399.62 136.71 M292.03 260.48 C317.01 231.17, 342.37 202.7, 399.62 136.71 M297.67 260.09 C320.79 233.82, 342.65 206.24, 399.36 143.11 M297.67 260.09 C337.01 212.55, 377.78 167.34, 399.36 143.11 M302.66 260.45 C335.5 224.45, 365.99 190.46, 399.75 148.75 M302.66 260.45 C341.03 214.95, 379.75 172.24, 399.75 148.75 M308.3 260.05 C331.34 234.81, 350.27 209.28, 399.49 155.15 M308.3 260.05 C337.96 226.28, 369 190.93, 399.49 155.15 M313.29 260.41 C344.16 226.42, 373.26 191.65, 399.23 161.54 M313.29 260.41 C340.87 229.56, 366.67 199.76, 399.23 161.54 M318.93 260.02 C339.08 235.8, 357.34 215.88, 399.63 167.19 M318.93 260.02 C338.18 238.19, 356.44 217.12, 399.63 167.19 M323.92 260.38 C343.38 240.84, 359.55 216.05, 399.36 173.59 M323.92 260.38 C339.58 242.35, 357.05 223.74, 399.36 173.59 M328.9 260.74 C356.6 230.93, 381.38 201.75, 399.76 179.23 M328.9 260.74 C355.35 229.48, 382.07 199.6, 399.76 179.23 M334.55 260.34 C353.55 235.84, 374.53 215.15, 399.5 185.63 M334.55 260.34 C350.96 240.58, 366.19 222.61, 399.5 185.63 M339.53 260.7 C355.36 243.43, 370.27 221.73, 399.24 192.02 M339.53 260.7 C361.66 234.82, 383.39 210.67, 399.24 192.02 M345.18 260.31 C362.54 238.6, 380.33 220.43, 399.63 197.67 M345.18 260.31 C359.44 243.81, 374.08 228.32, 399.63 197.67 M350.16 260.67 C364.5 243.76, 376.71 230.97, 399.37 204.06 M350.16 260.67 C363.93 245.32, 377.91 229.24, 399.37 204.06 M355.81 260.27 C372.91 242.23, 387.92 223.73, 399.76 209.71 M355.81 260.27 C373.85 240.57, 389.06 220.82, 399.76 209.71 M360.79 260.63 C368.09 252.39, 377.96 242.27, 399.5 216.11 M360.79 260.63 C372.53 247.08, 385.83 232.19, 399.5 216.11 M366.44 260.24 C372.92 252.64, 384.19 241.59, 399.24 222.5 M366.44 260.24 C374.31 251.44, 384.03 240.28, 399.24 222.5 M372.74 259.09 C379.45 251.45, 383.63 244.65, 398.98 228.9 M372.74 259.09 C383.24 247.41, 391.87 236.99, 398.98 228.9 M378.38 258.69 C382.28 255.84, 387.86 247.21, 398.06 236.05 M378.38 258.69 C382.66 254.99, 386.79 248.97, 398.06 236.05 M385.99 256.04 C389.48 251.84, 392.18 249.55, 394.52 246.23 M385.99 256.04 C389.44 252.25, 392.29 247.67, 394.52 246.23" stroke="#ffec99" stroke-width="0.5" fill="none"></path><path d="M32 0 M32 0 C123.14 1.36, 217.94 1.27, 366 0 M32 0 C131.29 -1.79, 231.25 -1.6, 366 0 M366 0 C387.8 0.82, 396.14 9, 398 32 M366 0 C388.4 -1.28, 396.3 11.05, 398 32 M398 32 C396.68 77, 397.3 120.08, 398 228 M398 32 C397.91 99.78, 396.94 166.68, 398 228 M398 228 C399.84 247.36, 385.48 260.38, 366 260 M398 228 C399.48 250.74, 389.16 259.77, 366 260 M366 260 C278.41 260.83, 191.33 259.2, 32 260 M366 260 C254.85 261.48, 142.72 261.51, 32 260 M32 260 C9.06 261.46, -1.27 247.64, 0 228 M32 260 C8.96 260.25, 0.79 249.13, 0 228 M0 228 C-2.62 187.23, -0.98 150.02, 0 32 M0 228 C-1.12 169.92, -1.74 111.79, 0 32 M0 32 C-0.23 11.83, 11.85 0.53, 32 0 M0 32 C1.74 9.13, 11.06 1.81, 32 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(284.59193843887 276.159696266393) rotate(0 182.81249999999994 124.80000000000001)"><text x="0" y="0" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">directories:</text><text x="0" y="19.2" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge"> - name: keep</text><text x="0" y="38.4" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">   digest: &lt;directory-with-keep-digest&gt;</text><text x="0" y="57.599999999999994" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">   size: 1</text><text x="0" y="76.8" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">   executable: false</text><text x="0" y="96" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">files:</text><text x="0" y="115.19999999999999" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge"> - name: .keep</text><text x="0" y="134.4" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">   digest: &lt;empty-blob-digest&gt;</text><text x="0" y="153.6" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">   size: 0</text><text x="0" y="172.79999999999998" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">   executable: false</text><text x="0" y="192" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">symlinks:</text><text x="0" y="211.2" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge"> - name: aa</text><text x="0" y="230.39999999999998" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">   target: /nix/store/somewhereelse</text></g><g stroke-linecap="round" transform="translate(292.58984375 581.578125) rotate(0 192 36.5)"><path d="M4.86 4.22 C4.86 4.22, 4.86 4.22, 4.86 4.22 M4.86 4.22 C4.86 4.22, 4.86 4.22, 4.86 4.22 M3.28 12.13 C5.02 8.82, 8.25 4.62, 11.81 2.32 M3.28 12.13 C6.19 8.3, 9.57 5.6, 11.81 2.32 M1.71 20.04 C8.34 16.13, 10.93 7.61, 18.77 0.41 M1.71 20.04 C6.51 15.46, 9.85 9.62, 18.77 0.41 M2.11 25.68 C10.79 16.53, 14.3 8.85, 23.76 0.77 M2.11 25.68 C8.12 18.52, 15.95 11.63, 23.76 0.77 M1.84 32.08 C8.84 22.24, 16.07 17.29, 29.4 0.38 M1.84 32.08 C11.36 20.47, 23.16 9.07, 29.4 0.38 M2.24 37.72 C9.27 29.77, 15.4 22.85, 34.39 0.74 M2.24 37.72 C15.42 23.1, 27.12 8.74, 34.39 0.74 M2.63 43.36 C8.76 33.58, 17 25.79, 40.03 0.35 M2.63 43.36 C13.14 31.06, 23.17 18.26, 40.03 0.35 M2.37 49.76 C16.36 34.07, 29.4 16.32, 45.02 0.71 M2.37 49.76 C17.93 32.43, 33.93 13.74, 45.02 0.71 M2.77 55.41 C19.05 35.42, 38.7 10.68, 50.66 0.31 M2.77 55.41 C19.96 34.91, 39.43 13.51, 50.66 0.31 M1.19 63.31 C17.21 49, 29.42 31.12, 55.65 0.67 M1.19 63.31 C15.9 46.95, 29.88 30.05, 55.65 0.67 M4.21 65.94 C21.41 44.81, 41.02 26.02, 61.29 0.28 M4.21 65.94 C24.28 42.91, 45.96 20.08, 61.29 0.28 M6.57 69.32 C23.78 51.2, 36.27 33.53, 66.28 0.64 M6.57 69.32 C19.35 53.63, 31.93 38.99, 66.28 0.64 M9.59 71.94 C25.17 56.48, 40.49 38.06, 71.92 0.24 M9.59 71.94 C23.84 58.2, 36.53 43.78, 71.92 0.24 M13.92 73.05 C31.98 52.05, 54.85 25.68, 76.91 0.6 M13.92 73.05 C30.95 53.92, 48.62 33.36, 76.91 0.6 M19.57 72.66 C38.35 51.77, 53.43 34.49, 82.55 0.21 M19.57 72.66 C32.02 56.9, 44.97 41.55, 82.55 0.21 M24.55 73.02 C48.11 46.73, 66.96 24.34, 87.54 0.57 M24.55 73.02 C47.62 46.37, 71.59 19.54, 87.54 0.57 M30.2 72.63 C46.25 51.67, 67.22 31.55, 93.18 0.17 M30.2 72.63 C53.38 46.94, 75.41 21.61, 93.18 0.17 M35.18 72.99 C55.38 48.59, 74.75 27.94, 98.17 0.53 M35.18 72.99 C57.1 47.59, 77.7 22.46, 98.17 0.53 M40.83 72.59 C64.45 43.72, 89.12 19.76, 103.81 0.14 M40.83 72.59 C61.26 47.84, 82.21 23.37, 103.81 0.14 M45.81 72.95 C66.24 52.79, 83.08 30.43, 108.8 0.5 M45.81 72.95 C67.8 48.81, 88.58 24.77, 108.8 0.5 M51.46 72.56 C64.09 57.9, 78.25 44.63, 113.78 0.86 M51.46 72.56 C72.83 47.42, 94.74 22.72, 113.78 0.86 M56.44 72.92 C74.65 52.58, 96.04 31.05, 119.43 0.46 M56.44 72.92 C81.73 45.16, 105.23 18.3, 119.43 0.46 M62.09 72.52 C80.07 51.93, 98.45 29.82, 124.41 0.82 M62.09 72.52 C79.04 54.05, 95.96 34.96, 124.41 0.82 M67.07 72.88 C80.12 59.08, 92.94 42.21, 130.06 0.43 M67.07 72.88 C80.66 58.14, 95.45 40.91, 130.06 0.43 M72.06 73.24 C93.79 49.66, 111.38 26.32, 135.04 0.79 M72.06 73.24 C89.58 51.72, 107.82 32.09, 135.04 0.79 M77.7 72.85 C104.72 44.35, 127.17 17, 140.69 0.4 M77.7 72.85 C96.13 51.38, 114.98 29.95, 140.69 0.4 M82.69 73.21 C104.71 47.57, 127.59 22.31, 145.67 0.76 M82.69 73.21 C101.71 51.47, 120.77 29.01, 145.67 0.76 M88.33 72.81 C110.76 47.24, 131.64 20.5, 151.32 0.36 M88.33 72.81 C109 48.14, 130.72 23.09, 151.32 0.36 M93.32 73.17 C111.15 55.01, 126.29 31.78, 156.3 0.72 M93.32 73.17 C110.49 51.39, 129.9 29.91, 156.3 0.72 M98.96 72.78 C118.26 54.28, 133.1 34.79, 161.95 0.33 M98.96 72.78 C111.06 59.03, 124.81 44.07, 161.95 0.33 M103.95 73.14 C115.72 58.92, 129.64 40.85, 166.93 0.69 M103.95 73.14 C126.22 47.2, 149.58 21.56, 166.93 0.69 M109.59 72.74 C123.31 56.85, 140.06 38.01, 172.58 0.29 M109.59 72.74 C124.38 56.56, 137.2 40.37, 172.58 0.29 M114.58 73.1 C138.07 45.66, 163.14 15.75, 177.56 0.65 M114.58 73.1 C131.86 52.94, 147.99 32.77, 177.56 0.65 M120.22 72.71 C133.34 57.46, 150.05 38.7, 183.21 0.26 M120.22 72.71 C135.3 57.12, 148.59 39.51, 183.21 0.26 M125.21 73.07 C147.35 45.64, 169.73 21.54, 188.19 0.62 M125.21 73.07 C146.83 48.56, 167.28 24.07, 188.19 0.62 M130.85 72.68 C149.9 47.27, 173.04 22.78, 193.84 0.22 M130.85 72.68 C153.31 46.45, 175.71 22.68, 193.84 0.22 M135.84 73.04 C157.75 47.97, 181.46 23.22, 198.82 0.58 M135.84 73.04 C157.01 47.37, 178.52 21.52, 198.82 0.58 M141.49 72.64 C163.6 47.54, 184.92 24.28, 203.81 0.94 M141.49 72.64 C157.97 55.11, 171.75 36.25, 203.81 0.94 M146.47 73 C164.74 50.07, 184.75 31.86, 209.45 0.55 M146.47 73 C169.77 45.07, 195.2 17.9, 209.45 0.55 M152.12 72.61 C170.98 50.38, 191.11 25.57, 214.44 0.91 M152.12 72.61 C167.16 54.48, 184.74 36.2, 214.44 0.91 M157.1 72.97 C171.7 55.53, 188.24 37.45, 220.08 0.51 M157.1 72.97 C171.53 57.7, 184.96 42.02, 220.08 0.51 M162.09 73.33 C182.79 52.34, 200.46 29.24, 225.07 0.87 M162.09 73.33 C176.54 57.94, 188.74 43.02, 225.07 0.87 M167.73 72.93 C187.78 52.57, 204.67 31.34, 230.71 0.48 M167.73 72.93 C192.31 45.48, 215.11 18.16, 230.71 0.48 M172.72 73.29 C192.03 53.03, 213.93 26.16, 235.7 0.84 M172.72 73.29 C187.14 54.62, 203.69 37.67, 235.7 0.84 M178.36 72.9 C196.89 49.98, 218.6 27.24, 241.34 0.45 M178.36 72.9 C191.33 57.21, 204.9 42.89, 241.34 0.45 M183.35 73.26 C207.11 47.48, 230.29 17.13, 246.33 0.81 M183.35 73.26 C195.69 56.95, 208.85 41.55, 246.33 0.81 M188.99 72.86 C203.94 55.74, 221.89 34.74, 251.97 0.41 M188.99 72.86 C203.18 55.08, 218.22 38.46, 251.97 0.41 M193.98 73.22 C219.55 44.07, 240.87 19.79, 256.96 0.77 M193.98 73.22 C208.25 55.94, 223.54 39.09, 256.96 0.77 M199.62 72.83 C217.99 48.42, 241.49 28.33, 262.6 0.38 M199.62 72.83 C214.61 56.41, 229.03 40.98, 262.6 0.38 M204.61 73.19 C223.33 53.31, 241.74 30.68, 267.59 0.74 M204.61 73.19 C229.17 46.22, 251.07 19.59, 267.59 0.74 M210.25 72.79 C224.49 54.46, 241.18 34.58, 273.23 0.34 M210.25 72.79 C227.21 52.37, 245.03 32.68, 273.23 0.34 M215.24 73.15 C229.43 57.1, 240.31 42.47, 278.22 0.7 M215.24 73.15 C237.32 48.52, 258.37 22.2, 278.22 0.7 M220.88 72.76 C240.5 49.7, 259.92 29.24, 283.86 0.31 M220.88 72.76 C245.23 46.31, 268.29 19.17, 283.86 0.31 M225.87 73.12 C244.42 48.72, 265.88 27.95, 288.85 0.67 M225.87 73.12 C251.66 44.25, 275.97 16.49, 288.85 0.67 M231.51 72.72 C253.24 45.13, 279.63 17.21, 293.84 1.03 M231.51 72.72 C244.39 57.29, 257.3 41.73, 293.84 1.03 M236.5 73.08 C256.68 52.52, 274.01 31.26, 299.48 0.63 M236.5 73.08 C252.49 52.88, 270.22 34.34, 299.48 0.63 M242.14 72.69 C264.56 48.95, 287.34 23.02, 304.47 0.99 M242.14 72.69 C265.99 45.72, 289.13 17.36, 304.47 0.99 M247.13 73.05 C268.76 49.79, 290 25.19, 310.11 0.6 M247.13 73.05 C271.93 45.31, 297.4 16.35, 310.11 0.6 M252.77 72.66 C272.81 49.72, 292.69 22.65, 315.1 0.96 M252.77 72.66 C274.82 46.14, 296.99 21.06, 315.1 0.96 M257.76 73.02 C279.06 47.65, 296.04 26.13, 320.74 0.56 M257.76 73.02 C279.13 48.78, 298.14 26.64, 320.74 0.56 M262.75 73.38 C286.03 44.74, 309.2 19.59, 325.73 0.92 M262.75 73.38 C284.29 48.03, 306.79 23.96, 325.73 0.92 M268.39 72.98 C284.12 55.13, 298.79 37.14, 331.37 0.53 M268.39 72.98 C286.16 50.17, 306.05 30.27, 331.37 0.53 M273.38 73.34 C296.56 45.22, 323.75 15.16, 336.36 0.89 M273.38 73.34 C289.13 55.59, 302.31 40.33, 336.36 0.89 M279.02 72.95 C303.53 43.87, 326.85 18.13, 342 0.49 M279.02 72.95 C293.37 55.49, 308.15 39.18, 342 0.49 M284.01 73.31 C298.42 57.71, 311.23 40.55, 346.99 0.85 M284.01 73.31 C308.02 43.63, 333.49 16.03, 346.99 0.85 M289.65 72.91 C311.51 49.12, 331.37 27.36, 352.63 0.46 M289.65 72.91 C314.28 43.1, 339.52 15.91, 352.63 0.46 M294.64 73.27 C311.15 55.94, 323.35 38.14, 357.62 0.82 M294.64 73.27 C315.12 50.08, 336.79 25.51, 357.62 0.82 M300.28 72.88 C323.32 47.78, 344.6 22, 363.26 0.43 M300.28 72.88 C320.59 50.02, 339.32 28.35, 363.26 0.43 M305.27 73.24 C321.19 53.97, 335.23 38.9, 368.25 0.79 M305.27 73.24 C320.38 55.99, 334.56 39.62, 368.25 0.79 M310.91 72.84 C326.89 57.23, 339.69 36.32, 372.58 1.9 M310.91 72.84 C323.53 58.18, 338.06 42.96, 372.58 1.9 M315.9 73.2 C339.86 47.6, 361.01 22.6, 376.91 3.02 M315.9 73.2 C338.69 46.17, 361.68 20.58, 376.91 3.02 M321.54 72.81 C338.51 50.61, 357.46 32.27, 379.93 5.64 M321.54 72.81 C336.35 54.99, 349.93 38.93, 379.93 5.64 M326.53 73.17 C341.29 57.14, 355.14 36.64, 382.29 9.02 M326.53 73.17 C347.2 48.97, 367.48 26.48, 382.29 9.02 M332.17 72.77 C348.91 51.78, 366.05 34.35, 384.66 12.4 M332.17 72.77 C345.89 56.87, 360.02 41.97, 384.66 12.4 M337.16 73.13 C350.96 56.83, 362.64 44.64, 384.39 18.8 M337.16 73.13 C350.38 58.41, 363.8 42.97, 384.39 18.8 M342.8 72.74 C358.9 55.85, 372.92 38.47, 384.13 25.19 M342.8 72.74 C359.81 54.2, 374.02 35.6, 384.13 25.19 M347.79 73.1 C354.66 65.33, 364.11 55.68, 384.53 30.84 M347.79 73.1 C358.88 60.26, 371.55 46.08, 384.53 30.84 M352.77 73.46 C358.94 66.21, 369.89 55.52, 384.27 37.23 M352.77 73.46 C360.31 65.04, 369.69 54.26, 384.27 37.23 M358.42 73.07 C364.98 65.59, 369.02 58.96, 384 43.63 M358.42 73.07 C368.68 61.66, 377.08 51.51, 384 43.63 M363.4 73.43 C367.59 70.26, 373.45 61.3, 384.4 49.27 M363.4 73.43 C367.97 69.41, 372.38 63.07, 384.4 49.27 M367.74 74.54 C374.28 66.78, 379.58 61.94, 384.14 55.67 M367.74 74.54 C374.31 67.19, 380 58.66, 384.14 55.67 M374.69 72.64 C376.19 69.99, 382.01 66.54, 385.19 60.56 M374.69 72.64 C377.37 68.95, 380.91 65.58, 385.19 60.56" stroke="#ffec99" stroke-width="0.5" fill="none"></path><path d="M18.25 0 M18.25 0 C129.54 2.72, 242.28 1.14, 365.75 0 M18.25 0 C127.66 -1.41, 238.58 -1.21, 365.75 0 M365.75 0 C378.27 0.9, 382.95 5.36, 384 18.25 M365.75 0 C380.07 0.47, 383.93 4.97, 384 18.25 M384 18.25 C384.02 33.84, 384.87 48.45, 384 54.75 M384 18.25 C384.01 28.7, 384.22 39.53, 384 54.75 M384 54.75 C382.32 67.61, 378.7 73.98, 365.75 73 M384 54.75 C385.93 68.19, 378.42 74.36, 365.75 73 M365.75 73 C288.24 70.59, 213.1 72.01, 18.25 73 M365.75 73 C265.08 74.84, 163.23 75.38, 18.25 73 M18.25 73 C7.8 72.9, 1.58 68.54, 0 54.75 M18.25 73 C7.06 72.31, -0.46 65.42, 0 54.75 M0 54.75 C0.31 45.73, -1.55 34.03, 0 18.25 M0 54.75 C0.72 40.09, -0.05 27.49, 0 18.25 M0 18.25 C1.55 7.28, 6.06 -1.74, 18.25 0 M0 18.25 C1.91 4.6, 5.29 1.6, 18.25 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(297.58984375 586.578125) rotate(0 70.3125 28.80000000000001)"><text x="0" y="0" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">directories: []</text><text x="0" y="19.2" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">files: []</text><text x="0" y="38.4" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">symlinks: []</text></g><g stroke-linecap="round" transform="translate(297.01171875 700.19140625) rotate(0 192 63)"><path d="M4.68 12.02 C4.68 12.02, 4.68 12.02, 4.68 12.02 M4.68 12.02 C4.68 12.02, 4.68 12.02, 4.68 12.02 M1.14 22.19 C5.68 16.12, 10.34 11.35, 18.19 2.57 M1.14 22.19 C5.63 15.12, 11.63 10.54, 18.19 2.57 M2.84 26.32 C9.4 19.34, 14.38 14.47, 24.49 1.42 M2.84 26.32 C7.02 21.88, 12.43 16.24, 24.49 1.42 M3.24 31.97 C12.37 22.32, 23.31 9.97, 30.14 1.02 M3.24 31.97 C8.75 26.69, 14.07 19.43, 30.14 1.02 M2.98 38.36 C11.61 29.09, 22.34 16.73, 34.47 2.14 M2.98 38.36 C14.61 26.7, 24.62 13.57, 34.47 2.14 M2.72 44.76 C8.44 35.48, 20.15 28.58, 39.46 2.5 M2.72 44.76 C9.81 35.38, 16.68 27.81, 39.46 2.5 M3.11 50.4 C17.54 33.89, 29.94 20.31, 45.1 2.1 M3.11 50.4 C20.74 31.63, 36.06 13.37, 45.1 2.1 M2.85 56.8 C18.58 35.97, 39.38 14.61, 50.09 2.46 M2.85 56.8 C15.02 42.55, 25.62 29.03, 50.09 2.46 M3.24 62.45 C13.28 49.17, 26.13 38.7, 55.73 2.07 M3.24 62.45 C14.41 50.21, 23.72 38.66, 55.73 2.07 M2.98 68.84 C13.36 56.63, 24.49 40.18, 60.72 2.43 M2.98 68.84 C25.38 43.36, 46.78 18.82, 60.72 2.43 M2.72 75.24 C27.68 45.78, 49.87 21.46, 66.36 2.03 M2.72 75.24 C27.36 48.58, 50.99 21.41, 66.36 2.03 M3.12 80.88 C26.98 53.98, 51.79 26.74, 71.35 2.39 M3.12 80.88 C24.82 57.11, 46.21 30.7, 71.35 2.39 M2.85 87.28 C25.83 58.82, 49.6 32.81, 76.33 2.75 M2.85 87.28 C18.86 69.84, 32.85 53.27, 76.33 2.75 M3.25 92.92 C31.02 64.11, 55.21 32.05, 81.98 2.36 M3.25 92.92 C29.09 64.51, 54.01 35.08, 81.98 2.36 M0.36 102.34 C18.06 82.88, 34.35 63.19, 86.96 2.72 M0.36 102.34 C22.5 76.81, 45.99 50.02, 86.96 2.72 M2.07 106.47 C35.94 64.04, 71.16 24.59, 92.61 2.33 M2.07 106.47 C29.3 74.98, 57.21 42, 92.61 2.33 M3.78 110.61 C32.64 72.16, 65.08 38.52, 97.59 2.69 M3.78 110.61 C27.71 83.59, 50.59 56.73, 97.59 2.69 M5.48 114.74 C31.81 84.38, 61.24 51.3, 103.24 2.29 M5.48 114.74 C38.37 76.78, 69.65 40.61, 103.24 2.29 M7.85 118.12 C37.24 84.78, 67.86 51.75, 108.22 2.65 M7.85 118.12 C40.51 79.72, 74.23 40.65, 108.22 2.65 M10.86 120.75 C33.15 93.28, 59.27 65.67, 113.87 2.26 M10.86 120.75 C41.44 83.49, 74.67 47.06, 113.87 2.26 M14.54 122.62 C54.9 77.33, 93.45 31.72, 118.85 2.62 M14.54 122.62 C36.96 96.13, 58.59 69.9, 118.85 2.62 M18.21 124.48 C60.03 78.24, 98.36 27.73, 124.5 2.22 M18.21 124.48 C47.99 89.82, 78.58 52.73, 124.5 2.22 M23.2 124.84 C60.93 81.34, 96.9 37.41, 129.48 2.58 M23.2 124.84 C51.15 90.76, 80.7 57.55, 129.48 2.58 M27.53 125.96 C67.64 79.48, 108.95 32.16, 135.13 2.19 M27.53 125.96 C50.35 100, 70.45 76.16, 135.13 2.19 M32.52 126.32 C67.96 82.8, 105.29 42.95, 140.11 2.55 M32.52 126.32 C55.09 102.84, 75.65 76.61, 140.11 2.55 M37.51 126.68 C76.58 83.49, 111.39 39.46, 145.76 2.15 M37.51 126.68 C68.68 90.35, 99.78 54.4, 145.76 2.15 M43.15 126.29 C79.05 86.81, 113.32 46.01, 150.74 2.51 M43.15 126.29 C83.16 78.74, 122.47 32.63, 150.74 2.51 M48.14 126.65 C84.95 86.46, 121.01 43.96, 156.39 2.12 M48.14 126.65 C77.47 92.07, 106.15 57.86, 156.39 2.12 M53.12 127.01 C94.06 83.46, 132.13 35.83, 161.37 2.48 M53.12 127.01 C83.35 90.25, 115.88 54.9, 161.37 2.48 M58.77 126.61 C88.9 91.94, 120.62 55.5, 167.02 2.08 M58.77 126.61 C80.71 101.71, 103.08 74.94, 167.02 2.08 M63.75 126.97 C94.83 94.26, 121.35 59.65, 172 2.44 M63.75 126.97 C99.19 87.16, 134.08 46.33, 172 2.44 M69.4 126.58 C96.06 98.29, 123.08 66.58, 177.65 2.05 M69.4 126.58 C94.05 98.86, 118.43 72.82, 177.65 2.05 M74.38 126.94 C110.22 87.09, 146.32 44.12, 182.63 2.41 M74.38 126.94 C112.7 80.36, 152.52 34.24, 182.63 2.41 M80.03 126.54 C112.63 85.84, 146.64 47.21, 188.28 2.01 M80.03 126.54 C118.52 83.47, 154.62 39.47, 188.28 2.01 M85.01 126.9 C123.66 84.49, 160.64 41.03, 193.26 2.37 M85.01 126.9 C107.24 101.3, 128.84 75.24, 193.26 2.37 M90.66 126.51 C128.32 79.98, 168.35 34.11, 198.25 2.73 M90.66 126.51 C125.23 87.51, 159.47 48.47, 198.25 2.73 M95.64 126.87 C136.32 80.89, 176.13 32.73, 203.89 2.34 M95.64 126.87 C130.67 85.7, 167.03 45.03, 203.89 2.34 M101.29 126.47 C138.72 85.68, 174.7 42.24, 208.88 2.7 M101.29 126.47 C141.27 81.26, 181.48 32.8, 208.88 2.7 M106.27 126.83 C133.13 96.99, 159.87 69.21, 214.52 2.31 M106.27 126.83 C139.22 87.92, 173.99 50.04, 214.52 2.31 M111.92 126.44 C139.17 92.95, 170.03 64.03, 219.51 2.67 M111.92 126.44 C140.35 94.34, 169.24 63.52, 219.51 2.67 M116.9 126.8 C139.41 102.01, 161.49 74.65, 225.15 2.27 M116.9 126.8 C143.96 97.47, 169.6 65.58, 225.15 2.27 M122.55 126.4 C151.81 92.7, 184.46 60.16, 230.14 2.63 M122.55 126.4 C144.55 101.98, 167.71 77.15, 230.14 2.63 M127.53 126.76 C155.24 93.9, 184.87 59.39, 235.78 2.24 M127.53 126.76 C162.55 85.69, 197.14 44.32, 235.78 2.24 M133.18 126.37 C156.24 97.59, 181.25 74.22, 240.77 2.6 M133.18 126.37 C169.91 83.87, 205.04 43.06, 240.77 2.6 M138.16 126.73 C171.62 83.67, 210.01 43.26, 246.41 2.2 M138.16 126.73 C162.75 98.55, 185.88 71.86, 246.41 2.2 M143.81 126.33 C167.53 96.38, 195.44 68.77, 251.4 2.56 M143.81 126.33 C175.38 87.65, 208.52 52.45, 251.4 2.56 M148.79 126.69 C186.15 81.29, 226.32 36.57, 257.04 2.17 M148.79 126.69 C185.38 86.13, 221.92 44.72, 257.04 2.17 M154.44 126.3 C190.78 83.15, 229.34 36.03, 262.03 2.53 M154.44 126.3 C178.51 97.01, 203.83 70.69, 262.03 2.53 M159.42 126.66 C189.83 93.77, 217.8 63.16, 267.67 2.13 M159.42 126.66 C196.22 84.11, 234.53 42.17, 267.67 2.13 M165.07 126.27 C199.58 88.17, 233.46 45.58, 272.66 2.49 M165.07 126.27 C204.81 83.18, 243.36 38.31, 272.66 2.49 M170.05 126.63 C191.4 102.17, 214.65 75.55, 278.3 2.1 M170.05 126.63 C194.79 98.87, 219.21 68.85, 278.3 2.1 M175.04 126.99 C218.62 76.89, 259.45 31.99, 283.29 2.46 M175.04 126.99 C200.78 100.7, 225.33 71.6, 283.29 2.46 M180.68 126.59 C214.02 88.6, 246.67 51.74, 288.93 2.06 M180.68 126.59 C212.29 91.19, 241.62 56.43, 288.93 2.06 M185.67 126.95 C215.71 95.07, 243.49 60.99, 293.92 2.42 M185.67 126.95 C216.01 94.84, 243.45 62.69, 293.92 2.42 M191.31 126.56 C229.63 85.76, 263.97 41.71, 299.56 2.03 M191.31 126.56 C222.59 88.19, 255.96 51.19, 299.56 2.03 M196.3 126.92 C217.19 100.03, 243 74.87, 304.55 2.39 M196.3 126.92 C218.88 99.48, 243.28 72.58, 304.55 2.39 M201.94 126.52 C245.64 78.21, 284.33 30.24, 309.54 2.75 M201.94 126.52 C240.53 84.15, 277.64 41.48, 309.54 2.75 M206.93 126.88 C248.75 80.42, 287.71 35.97, 315.18 2.36 M206.93 126.88 C240.59 87.82, 273.05 49.59, 315.18 2.36 M212.57 126.49 C242.06 93.98, 270.55 61.9, 320.17 2.72 M212.57 126.49 C238.53 99.42, 261.94 72.17, 320.17 2.72 M217.56 126.85 C255.21 80.1, 296.4 36.1, 325.81 2.32 M217.56 126.85 C248.46 91.27, 280.97 56.5, 325.81 2.32 M223.2 126.45 C245.32 96.89, 272.92 68.02, 330.8 2.68 M223.2 126.45 C246.93 99.22, 269.26 73.84, 330.8 2.68 M228.19 126.81 C266.27 81.56, 302.8 39.44, 336.44 2.29 M228.19 126.81 C268.68 80.87, 310.31 31.49, 336.44 2.29 M233.83 126.42 C261.99 92.9, 293.86 58.89, 341.43 2.65 M233.83 126.42 C265.72 90.09, 297.62 53.24, 341.43 2.65 M238.82 126.78 C276.56 81.01, 314.76 40.31, 347.07 2.25 M238.82 126.78 C275.07 83.85, 309.78 43.16, 347.07 2.25 M244.47 126.38 C281.08 86.31, 311.83 47.08, 353.37 1.1 M244.47 126.38 C284.42 79.98, 324.2 34.41, 353.37 1.1 M249.45 126.74 C283.83 89.61, 316.66 48.25, 358.36 1.46 M249.45 126.74 C288.06 81.49, 329.09 35.62, 358.36 1.46 M255.1 126.35 C286.95 88.5, 321.78 49.74, 362.69 2.58 M255.1 126.35 C278.54 101.51, 299.57 76.6, 362.69 2.58 M260.08 126.71 C289.9 92.16, 318.76 59.09, 367.68 2.94 M260.08 126.71 C286.6 97.68, 309.24 69.16, 367.68 2.94 M265.73 126.32 C304.67 81.34, 343.89 36.13, 371.35 4.81 M265.73 126.32 C296.05 89.95, 328.32 54.46, 371.35 4.81 M270.71 126.68 C312.11 79.85, 350.1 38.08, 375.03 6.68 M270.71 126.68 C308.29 85.27, 344.32 43.1, 375.03 6.68 M276.36 126.28 C302.37 95.05, 332.55 61.6, 377.39 10.06 M276.36 126.28 C300.17 99.46, 322.39 72.19, 377.39 10.06 M281.34 126.64 C304.76 96.82, 332.3 67.75, 380.41 12.68 M281.34 126.64 C307.49 95.18, 332.91 65.5, 380.41 12.68 M286.33 127 C314.42 93.01, 345.57 57.56, 381.46 17.57 M286.33 127 C313.51 94.85, 341.77 62.68, 381.46 17.57 M291.97 126.61 C313.48 100.45, 339.17 75.97, 383.16 21.7 M291.97 126.61 C311.58 103.09, 333.11 79.56, 383.16 21.7 M296.96 126.97 C320.59 100.34, 342.62 72.36, 384.87 25.84 M296.96 126.97 C321.66 99.71, 344.27 72.56, 384.87 25.84 M302.6 126.57 C334.11 86.35, 368.78 51.97, 385.27 31.48 M302.6 126.57 C330.75 94.29, 357.85 62.13, 385.27 31.48 M307.59 126.93 C326.81 105.47, 343.03 83.78, 385 37.88 M307.59 126.93 C327.54 105.51, 344.6 84.21, 385 37.88 M313.23 126.54 C332.05 106.16, 349.05 86.64, 385.4 43.52 M313.23 126.54 C332.89 103.96, 352.85 83.32, 385.4 43.52 M318.22 126.9 C331.98 110.58, 346.57 93.14, 385.14 49.92 M318.22 126.9 C331.03 111.1, 344.19 94.95, 385.14 49.92 M323.86 126.5 C335.72 108.89, 350.56 93.22, 385.53 55.56 M323.86 126.5 C345.11 101.47, 364.04 79.57, 385.53 55.56 M328.85 126.86 C346.1 108.02, 361.21 90.1, 385.27 61.96 M328.85 126.86 C342.2 112.06, 355.94 96.74, 385.27 61.96 M334.49 126.47 C350.84 109.97, 364.71 94.6, 385.67 67.6 M334.49 126.47 C353.49 104.81, 372.17 82.14, 385.67 67.6 M339.48 126.83 C348.96 114.71, 363.17 101.01, 385.4 74 M339.48 126.83 C354.75 110.09, 368.15 93.09, 385.4 74 M345.12 126.43 C354.84 117.22, 365.07 103.77, 385.8 79.64 M345.12 126.43 C358.46 110.23, 371.31 95.91, 385.8 79.64 M350.11 126.79 C364.3 111.08, 375.22 97.18, 385.54 86.04 M350.11 126.79 C363.14 112.28, 374.94 98.31, 385.54 86.04 M354.44 127.91 C362.81 117.31, 370.87 110.18, 385.28 92.44 M354.44 127.91 C362.9 118.85, 369.83 111.91, 385.28 92.44 M360.74 126.76 C367.63 119.85, 374.61 113.88, 386.98 96.57 M360.74 126.76 C369 116.72, 377.31 108.85, 386.98 96.57 M367.7 124.86 C371.04 118.25, 376.15 112.61, 382.13 108.25 M367.7 124.86 C371.11 120.48, 374.22 117.39, 382.13 108.25" stroke="#ffec99" stroke-width="0.5" fill="none"></path><path d="M31.5 0 M31.5 0 C144.73 2.16, 257.89 1.55, 352.5 0 M31.5 0 C123.21 -0.03, 215.81 -0.04, 352.5 0 M352.5 0 C373.48 -1.54, 384.54 11.54, 384 31.5 M352.5 0 C371.52 1.89, 382.06 11.18, 384 31.5 M384 31.5 C384.2 57.65, 382.83 81.45, 384 94.5 M384 31.5 C384.94 50.55, 383.94 67.7, 384 94.5 M384 94.5 C385.07 116.86, 374.35 127.03, 352.5 126 M384 94.5 C385.17 117.04, 373.57 125.11, 352.5 126 M352.5 126 C247.11 128.47, 140.42 127.09, 31.5 126 M352.5 126 C271.97 126.76, 193.29 127.03, 31.5 126 M31.5 126 C11.34 124.76, -1.26 114.07, 0 94.5 M31.5 126 C9.77 126.57, -1.64 113.31, 0 94.5 M0 94.5 C0.36 74.76, 1.16 53.5, 0 31.5 M0 94.5 C-0.19 81.51, -0.09 68.38, 0 31.5 M0 31.5 C-0.91 8.69, 10.9 0.79, 31.5 0 M0 31.5 C2.16 12.64, 11.39 -0.47, 31.5 0" stroke="#000000" stroke-width="1" fill="none"></path></g><g transform="translate(302.01171875 705.19140625) rotate(0 145.3125 57.60000000000002)"><text x="0" y="0" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">directories:</text><text x="0" y="19.2" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge"> - name: a</text><text x="0" y="38.4" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">   digest: &lt;directory-a-digest&gt;</text><text x="0" y="57.599999999999994" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">   size: 0</text><text x="0" y="76.8" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">files: []</text><text x="0" y="96" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#000000" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">symlinks: []</text></g><g transform="translate(303.75390625 45.62890625) rotate(0 89.0625 9.599999999999994)"><text x="0" y="0" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#f08c00" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">DIRECTORY_WITH_KEEP</text></g><g transform="translate(308.25390625 240.4765625) rotate(0 98.4375 9.599999999999994)"><text x="0" y="0" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#f08c00" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">DIRECTORY_COMPLICATED</text></g><g transform="translate(307.28125 554.6148437500001) rotate(0 51.5625 9.600000000000023)"><text x="0" y="0" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#f08c00" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">DIRECTORY_A</text></g><g transform="translate(310.6875 674.4585937500001) rotate(0 51.5625 9.600000000000023)"><text x="0" y="0" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#f08c00" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">DIRECTORY_B</text></g><g transform="translate(18.95703125 42.53671874999998) rotate(0 28.125 9.599999999999994)"><text x="0" y="0" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#1971c2" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">BLOB_A</text></g><g transform="translate(22.55078125 130.24374999999998) rotate(0 28.125 9.599999999999994)"><text x="0" y="0" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#1971c2" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">BLOB_B</text></g><g transform="translate(13.5546875 210.27109374999998) rotate(0 46.875 9.599999999999994)"><text x="0" y="0" font-family="Cascadia, Segoe UI Emoji" font-size="16px" fill="#1971c2" text-anchor="start" style="white-space: pre;" direction="ltr" dominant-baseline="text-before-edge">BLOB_EMPTY</text></g><g stroke-linecap="round"><g transform="translate(388.15234375 147.38671875) rotate(0 -178.16880695513453 57.143675736445005)"><path d="M-0.52 1.13 C-60.08 19.9, -297.46 94.83, -357.2 113.61 M1.4 0.68 C-58.25 18.98, -298.08 93.09, -357.74 111.59" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g transform="translate(388.15234375 147.38671875) rotate(0 -178.16880695513453 57.143675736445005)"><path d="M-331.96 94.73 C-340.78 97.36, -347.89 102.93, -357.37 112.7 M-333.95 93.43 C-339.75 97.47, -345.42 102.99, -358.69 111" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g transform="translate(388.15234375 147.38671875) rotate(0 -178.16880695513453 57.143675736445005)"><path d="M-325.89 114.33 C-336.76 110.86, -345.74 110.35, -357.37 112.7 M-327.89 113.04 C-335.08 112.25, -342.26 112.89, -358.69 111" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g></g><mask></mask><g stroke-linecap="round"><g transform="translate(399.61021320513464 319.000855513555) rotate(0 132.18131301403048 -119.00356223583219)"><path d="M-0.42 1.04 C43.76 -38.95, 220.7 -199.19, 264.79 -239.05 M1.55 0.54 C45.63 -39.42, 220.36 -198.5, 263.94 -238.07" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g transform="translate(399.61021320513464 319.000855513555) rotate(0 132.18131301403048 -119.00356223583219)"><path d="M251.72 -211.19 C251.81 -219.53, 257.18 -225.36, 265.07 -236.65 M250.73 -212.1 C253.06 -218.02, 257.3 -223.31, 264.08 -238.4" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g transform="translate(399.61021320513464 319.000855513555) rotate(0 132.18131301403048 -119.00356223583219)"><path d="M237.91 -226.37 C241.88 -230.52, 251.02 -232.22, 265.07 -236.65 M236.92 -227.29 C242.47 -229.5, 250.03 -231.14, 264.08 -238.4" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g></g><mask></mask><g stroke-linecap="round"><g transform="translate(396.39146320513464 414.62194926355494) rotate(0 -181.26559592220468 -76.52005761642474)"><path d="M0.21 0.21 C-60.17 -24.98, -301.37 -126.03, -361.5 -151.72 M-1.14 -0.73 C-61.8 -26.18, -302.73 -127.84, -362.74 -153.25" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g transform="translate(396.39146320513464 414.62194926355494) rotate(0 -181.26559592220468 -76.52005761642474)"><path d="M-332.43 -152.64 C-341.83 -151.66, -356.48 -151.02, -362.88 -151.77 M-332.23 -151.12 C-344.25 -151.99, -356 -152.45, -362.93 -152.59" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g transform="translate(396.39146320513464 414.62194926355494) rotate(0 -181.26559592220468 -76.52005761642474)"><path d="M-340.42 -133.74 C-346.81 -139.68, -358.54 -145.95, -362.88 -151.77 M-340.22 -132.22 C-349.22 -140.26, -357.91 -147.96, -362.93 -152.59" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g></g><mask></mask><g stroke-linecap="round"><g transform="translate(407.44140625 751.55078125) rotate(0 45.37286624398547 -48.585232615470886)"><path d="M-0.84 0.01 C13.92 -16, 74.3 -80.87, 89.45 -97.18 M0.92 -1.04 C15.95 -16.73, 76.9 -79.62, 91.58 -95.58" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g transform="translate(407.44140625 751.55078125) rotate(0 45.37286624398547 -48.585232615470886)"><path d="M79.65 -69.91 C84.59 -79.16, 87.99 -87.88, 90.6 -97.23 M78.91 -67.92 C83.62 -76.08, 87.25 -85.83, 90.62 -96.45" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g><g transform="translate(407.44140625 751.55078125) rotate(0 45.37286624398547 -48.585232615470886)"><path d="M64.75 -84.03 C75.18 -87.85, 84.2 -91.25, 90.6 -97.23 M64.01 -82.04 C73.25 -85.89, 81.47 -91.29, 90.62 -96.45" stroke="#1e1e1e" stroke-width="1" fill="none"></path></g></g><mask></mask></svg>
\ No newline at end of file