diff options
Diffstat (limited to 'users/flokli/ipu6-softisp')
27 files changed, 24173 insertions, 0 deletions
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, <rdid); ++ ++ 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-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->n00000001 --> ++<g id="edge1" class="edge"><title>n0000007d:port1->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-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->n00000002 --> ++<g id="edge2" class="edge"><title>n00000087:port1->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-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->n00000003 --> ++<g id="edge3" class="edge"><title>n00000091:port1->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-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->n00000004 --> ++<g id="edge4" class="edge"><title>n0000009b:port1->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-0036</text> ++<text text-anchor="middle" x="75" y="-487.3" font-family="Times,serif" font-size="14.00">/dev/v4l-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->n00000091 --> ++<g id="edge5" class="edge"><title>n00000865:port0->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->n0000007d --> ++<!-- n00000867 --> ++<!-- n00000867->n00000087 --> ++<!-- n00000868 --> ++<!-- n00000868->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 + |