From 7ec319bf224077d75a9572f92cb1d9705840d98f Mon Sep 17 00:00:00 2001 From: Harald Hoyer Date: Thu, 7 Mar 2024 09:43:15 +0100 Subject: [PATCH] Revert "enable MIPI camera" not worth the hassle This reverts commit 897a92ea5745fcc6dbd954116914a4a5f0914c22. --- modules/nixos/ipu6-softisp/README.md | 26 - modules/nixos/ipu6-softisp/default.nix | 81 - .../nixos/ipu6-softisp/kernel/softisp.patch | 17143 ---------------- ...ne-simple-fix-size-adjustment-in-val.patch | 43 - ...al-Move-dma_heaps.-h-cpp-to-common-d.patch | 194 - ...aps-extend-DmaHeap-class-to-support-.patch | 121 - ...al-Move-SharedMemObject-class-to-a-c.patch | 57 - ...al-Document-the-SharedMemObject-clas.patch | 139 - ...ibcamera-introduce-SoftwareIsp-class.patch | 354 - ...-software_isp-Add-SwStats-base-class.patch | 382 - ...ra-software_isp-Add-SwStatsCpu-class.patch | 272 - ...-software_isp-Add-Debayer-base-class.patch | 272 - ...ra-software_isp-Add-DebayerCpu-class.patch | 727 - ...camera-ipa-add-Soft-IPA-common-files.patch | 256 - ...ft-IPA-add-a-Simple-Soft-IPA-impleme.patch | 407 - ...re_isp-add-Simple-SoftwareIsp-implem.patch | 483 - ...ne-simple-rename-converterBuffers_-a.patch | 238 - ...ne-simple-enable-use-of-Soft-ISP-and.patch | 243 - ...s_cpu-Add-support-for-8-10-and-12-bp.patch | 199 - ...r_cpu-Add-support-for-8-10-and-12-bp.patch | 237 - ...ebayer_cpu-Add-BGR888-output-support.patch | 125 - ...ne-simple-Enable-simplepipeline-for-.patch | 30 - ...pport-for-IGIG_GBGR_IGIG_GRGB-bayer-.patch | 237 - ...s_cpu-Add-support-for-10bpp-IGIG_GBG.patch | 131 - ...r_cpu-Add-support-for-10bpp-IGIG_GBG.patch | 315 - ...ftware-ISP-benchmarking-documentatio.patch | 130 - .../libcamera/0024-ov01a1s-HACK.patch | 95 - ...r_cpu-Make-the-minimum-size-1280x720.patch | 40 - overlays/ipu6/default.nix | 6 - systems/x86_64-linux/x1/default.nix | 1 - 30 files changed, 22984 deletions(-) delete mode 100644 modules/nixos/ipu6-softisp/README.md delete mode 100644 modules/nixos/ipu6-softisp/default.nix delete mode 100644 modules/nixos/ipu6-softisp/kernel/softisp.patch delete mode 100644 modules/nixos/ipu6-softisp/libcamera/0001-libcamera-pipeline-simple-fix-size-adjustment-in-val.patch delete mode 100644 modules/nixos/ipu6-softisp/libcamera/0002-libcamera-internal-Move-dma_heaps.-h-cpp-to-common-d.patch delete mode 100644 modules/nixos/ipu6-softisp/libcamera/0003-libcamera-dma_heaps-extend-DmaHeap-class-to-support-.patch delete mode 100644 modules/nixos/ipu6-softisp/libcamera/0004-libcamera-internal-Move-SharedMemObject-class-to-a-c.patch delete mode 100644 modules/nixos/ipu6-softisp/libcamera/0005-libcamera-internal-Document-the-SharedMemObject-clas.patch delete mode 100644 modules/nixos/ipu6-softisp/libcamera/0006-libcamera-introduce-SoftwareIsp-class.patch delete mode 100644 modules/nixos/ipu6-softisp/libcamera/0007-libcamera-software_isp-Add-SwStats-base-class.patch delete mode 100644 modules/nixos/ipu6-softisp/libcamera/0008-libcamera-software_isp-Add-SwStatsCpu-class.patch delete mode 100644 modules/nixos/ipu6-softisp/libcamera/0009-libcamera-software_isp-Add-Debayer-base-class.patch delete mode 100644 modules/nixos/ipu6-softisp/libcamera/0010-libcamera-software_isp-Add-DebayerCpu-class.patch delete mode 100644 modules/nixos/ipu6-softisp/libcamera/0011-libcamera-ipa-add-Soft-IPA-common-files.patch delete mode 100644 modules/nixos/ipu6-softisp/libcamera/0012-libcamera-ipa-Soft-IPA-add-a-Simple-Soft-IPA-impleme.patch delete mode 100644 modules/nixos/ipu6-softisp/libcamera/0013-libcamera-software_isp-add-Simple-SoftwareIsp-implem.patch delete mode 100644 modules/nixos/ipu6-softisp/libcamera/0014-libcamera-pipeline-simple-rename-converterBuffers_-a.patch delete mode 100644 modules/nixos/ipu6-softisp/libcamera/0015-libcamera-pipeline-simple-enable-use-of-Soft-ISP-and.patch delete mode 100644 modules/nixos/ipu6-softisp/libcamera/0016-libcamera-swstats_cpu-Add-support-for-8-10-and-12-bp.patch delete mode 100644 modules/nixos/ipu6-softisp/libcamera/0017-libcamera-debayer_cpu-Add-support-for-8-10-and-12-bp.patch delete mode 100644 modules/nixos/ipu6-softisp/libcamera/0018-libcamera-debayer_cpu-Add-BGR888-output-support.patch delete mode 100644 modules/nixos/ipu6-softisp/libcamera/0019-libcamera-pipeline-simple-Enable-simplepipeline-for-.patch delete mode 100644 modules/nixos/ipu6-softisp/libcamera/0020-libcamera-Add-support-for-IGIG_GBGR_IGIG_GRGB-bayer-.patch delete mode 100644 modules/nixos/ipu6-softisp/libcamera/0021-libcamera-swstats_cpu-Add-support-for-10bpp-IGIG_GBG.patch delete mode 100644 modules/nixos/ipu6-softisp/libcamera/0022-libcamera-debayer_cpu-Add-support-for-10bpp-IGIG_GBG.patch delete mode 100644 modules/nixos/ipu6-softisp/libcamera/0023-libcamera-Add-Software-ISP-benchmarking-documentatio.patch delete mode 100644 modules/nixos/ipu6-softisp/libcamera/0024-ov01a1s-HACK.patch delete mode 100644 modules/nixos/ipu6-softisp/libcamera/0025-libcamera-debayer_cpu-Make-the-minimum-size-1280x720.patch delete mode 100644 overlays/ipu6/default.nix diff --git a/modules/nixos/ipu6-softisp/README.md b/modules/nixos/ipu6-softisp/README.md deleted file mode 100644 index 9c09e9a..0000000 --- a/modules/nixos/ipu6-softisp/README.md +++ /dev/null @@ -1,26 +0,0 @@ -# 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.7.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. - -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/modules/nixos/ipu6-softisp/default.nix b/modules/nixos/ipu6-softisp/default.nix deleted file mode 100644 index 462d178..0000000 --- a/modules/nixos/ipu6-softisp/default.nix +++ /dev/null @@ -1,81 +0,0 @@ -{ options, config, lib, pkgs, ... }: - -with lib; -with lib.plusultra; -let - cfg = config.plusultra.ipu6; - libcamera = pkgs.libcamera-unstable.overrideAttrs (old: { - mesonFlags = old.mesonFlags or [ ] ++ [ - "-Dpipelines=simple/simple,ipu3,uvcvideo" - "-Dipas=simple/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. - 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-internal-Document-the-SharedMemObject-clas.patch - ./libcamera/0006-libcamera-introduce-SoftwareIsp-class.patch - ./libcamera/0007-libcamera-software_isp-Add-SwStats-base-class.patch - ./libcamera/0008-libcamera-software_isp-Add-SwStatsCpu-class.patch - ./libcamera/0009-libcamera-software_isp-Add-Debayer-base-class.patch - ./libcamera/0010-libcamera-software_isp-Add-DebayerCpu-class.patch - ./libcamera/0011-libcamera-ipa-add-Soft-IPA-common-files.patch - ./libcamera/0012-libcamera-ipa-Soft-IPA-add-a-Simple-Soft-IPA-impleme.patch - ./libcamera/0013-libcamera-software_isp-add-Simple-SoftwareIsp-implem.patch - ./libcamera/0014-libcamera-pipeline-simple-rename-converterBuffers_-a.patch - ./libcamera/0015-libcamera-pipeline-simple-enable-use-of-Soft-ISP-and.patch - ./libcamera/0016-libcamera-swstats_cpu-Add-support-for-8-10-and-12-bp.patch - ./libcamera/0017-libcamera-debayer_cpu-Add-support-for-8-10-and-12-bp.patch - ./libcamera/0018-libcamera-debayer_cpu-Add-BGR888-output-support.patch - ./libcamera/0019-libcamera-pipeline-simple-Enable-simplepipeline-for-.patch - ./libcamera/0020-libcamera-Add-support-for-IGIG_GBGR_IGIG_GRGB-bayer-.patch - ./libcamera/0021-libcamera-swstats_cpu-Add-support-for-10bpp-IGIG_GBG.patch - ./libcamera/0022-libcamera-debayer_cpu-Add-support-for-10bpp-IGIG_GBG.patch - ./libcamera/0023-libcamera-Add-Software-ISP-benchmarking-documentatio.patch - ./libcamera/0024-ov01a1s-HACK.patch - ./libcamera/0025-libcamera-debayer_cpu-Make-the-minimum-size-1280x720.patch - ]; - }); - - # use patched libcamera - pipewire' = (pkgs.pipewire-unstable.override { - inherit libcamera; - }); - - wireplumber' = (pkgs.wireplumber.override { - pipewire = pipewire'; - }); -in -{ - options.plusultra.ipu6 = with types; { - enable = mkBoolOpt false "Whether or not to enable ipu6."; - }; - - config = mkIf cfg.enable { - - hardware.firmware = [ pkgs.ipu6-camera-bins ]; - - 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" - ''; - - services.pipewire.package = pipewire'; - services.pipewire.wireplumber.package = wireplumber'; - }; -} diff --git a/modules/nixos/ipu6-softisp/kernel/softisp.patch b/modules/nixos/ipu6-softisp/kernel/softisp.patch deleted file mode 100644 index b94126b..0000000 --- a/modules/nixos/ipu6-softisp/kernel/softisp.patch +++ /dev/null @@ -1,17143 +0,0 @@ -From 4e34bb68beec288e6fbc71170ff6d3ed39b76ce6 Mon Sep 17 00:00:00 2001 -From: Bingbu Cao -Date: Thu, 11 Jan 2024 14:55:15 +0800 -Subject: [PATCH 01/31] 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 -Link: https://lore.kernel.org/r/20240111065531.2418836-2-bingbu.cao@intel.com -Signed-off-by: Hans de Goede ---- - .../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 -+ -+/* -+ * 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 -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include -+ -+#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 "); -+MODULE_AUTHOR("Tianshu Qiu "); -+MODULE_AUTHOR("Bingbu Cao "); -+MODULE_AUTHOR("Qingwu Zhang "); -+MODULE_AUTHOR("Yunliang Ding "); -+MODULE_AUTHOR("Hongju Wang "); -+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 -+#include -+#include -+ -+#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.0 - -From 79ff4f8a1383c679fc4bbee832e51e6369cbc21c Mon Sep 17 00:00:00 2001 -From: Bingbu Cao -Date: Thu, 11 Jan 2024 14:55:16 +0800 -Subject: [PATCH 02/31] 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 -Link: https://lore.kernel.org/r/20240111065531.2418836-3-bingbu.cao@intel.com -Signed-off-by: Hans de Goede ---- - 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 -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#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 -+#include -+#include -+#include -+#include -+#include -+#include -+ -+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.0 - -From 5836e6b1138ff9cba76ab74c1eab0ec57e50dfc1 Mon Sep 17 00:00:00 2001 -From: Bingbu Cao -Date: Thu, 11 Jan 2024 14:55:17 +0800 -Subject: [PATCH 03/31] 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 -Link: https://lore.kernel.org/r/20240111065531.2418836-4-bingbu.cao@intel.com -Signed-off-by: Hans de Goede ---- - 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 -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#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 -+#include -+#include -+#include -+ -+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 -+ -+/* 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.0 - -From 4e01328e611be4ba5a2c9fe2baedfeb239db9d99 Mon Sep 17 00:00:00 2001 -From: Bingbu Cao -Date: Thu, 11 Jan 2024 14:55:18 +0800 -Subject: [PATCH 04/31] 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 -Link: https://lore.kernel.org/r/20240111065531.2418836-5-bingbu.cao@intel.com -Signed-off-by: Hans de Goede ---- - 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 -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#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.0 - -From 05c36b7a6306ed7ef15b9fd6039920a7d08626cc Mon Sep 17 00:00:00 2001 -From: Bingbu Cao -Date: Thu, 11 Jan 2024 14:55:19 +0800 -Subject: [PATCH 05/31] 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 -Link: https://lore.kernel.org/r/20240111065531.2418836-6-bingbu.cao@intel.com -Signed-off-by: Hans de Goede ---- - 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 -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#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 -+#include -+ -+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 -+ -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#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 -+#include -+#include -+ -+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.0 - -From 6e686c2ff23611004d6e4f9a543116d65cbcf5f3 Mon Sep 17 00:00:00 2001 -From: Bingbu Cao -Date: Thu, 11 Jan 2024 14:55:20 +0800 -Subject: [PATCH 06/31] 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 -Link: https://lore.kernel.org/r/20240111065531.2418836-7-bingbu.cao@intel.com -Signed-off-by: Hans de Goede ---- - 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 -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#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.0 - -From 6784d60f5fe68eca8bfc92785b5badd0bf415048 Mon Sep 17 00:00:00 2001 -From: Bingbu Cao -Date: Thu, 11 Jan 2024 14:55:21 +0800 -Subject: [PATCH 07/31] 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 -Link: https://lore.kernel.org/r/20240111065531.2418836-8-bingbu.cao@intel.com -Signed-off-by: Hans de Goede ---- - 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 -+#include -+#include -+#include -+#include -+#include -+ -+#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 -+ -+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.0 - -From 2d13721450bd1d8e778c0727a7c3eac6cc1fd6c0 Mon Sep 17 00:00:00 2001 -From: Bingbu Cao -Date: Thu, 11 Jan 2024 14:55:22 +0800 -Subject: [PATCH 08/31] 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 -Link: https://lore.kernel.org/r/20240111065531.2418836-9-bingbu.cao@intel.com -Signed-off-by: Hans de Goede ---- - 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 -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include -+#include -+#include -+#include -+#include -+ -+#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 -+ -+#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 -+#include -+#include -+ -+#include -+#include -+#include -+#include -+ -+#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 -+ -+#include -+#include -+#include -+ -+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 -+ -+#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.0 - -From ba67c52b68d458e2c8ae4af08bd01a6ebe60b475 Mon Sep 17 00:00:00 2001 -From: Bingbu Cao -Date: Thu, 11 Jan 2024 14:55:23 +0800 -Subject: [PATCH 09/31] 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 -Link: https://lore.kernel.org/r/20240111065531.2418836-10-bingbu.cao@intel.com -Signed-off-by: Hans de Goede ---- - .../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 -+#include -+#include -+#include -+#include -+#include -+ -+#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 -+#include -+#include -+#include -+ -+#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 -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include -+ -+#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.0 - -From a3b3658e56d3fe9e0cb03166e38a023dba853594 Mon Sep 17 00:00:00 2001 -From: Bingbu Cao -Date: Thu, 11 Jan 2024 14:55:24 +0800 -Subject: [PATCH 10/31] 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 -Signed-off-by: Hans de Goede -Link: https://lore.kernel.org/r/20240111065531.2418836-11-bingbu.cao@intel.com -Signed-off-by: Hans de Goede ---- - 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 -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#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 "); -+MODULE_AUTHOR("Tianshu Qiu "); -+MODULE_AUTHOR("Bingbu Cao "); -+MODULE_AUTHOR("Yunliang Ding "); -+MODULE_AUTHOR("Hongju Wang "); -+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 -+#include -+#include -+#include -+#include -+#include -+ -+#include -+#include -+#include -+ -+#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.0 - -From 32d07a2e879187ea87b90256ac32a41080e3b8bc Mon Sep 17 00:00:00 2001 -From: Bingbu Cao -Date: Thu, 11 Jan 2024 14:55:25 +0800 -Subject: [PATCH 11/31] 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 -Link: https://lore.kernel.org/r/20240111065531.2418836-12-bingbu.cao@intel.com -Signed-off-by: Hans de Goede ---- - .../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 -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include -+#include -+#include -+#include -+ -+#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 -+#include -+#include -+#include -+#include -+ -+#include -+ -+#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 -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#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 -+#include -+#include -+#include -+#include -+ -+#include -+#include -+ -+#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.0 - -From abf929b4fbd35689080a8629c6bfebe0de699fe5 Mon Sep 17 00:00:00 2001 -From: Bingbu Cao -Date: Thu, 11 Jan 2024 14:55:26 +0800 -Subject: [PATCH 12/31] 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 -Signed-off-by: Andreas Helbech Kleist -Link: https://lore.kernel.org/r/20240111065531.2418836-13-bingbu.cao@intel.com -Signed-off-by: Hans de Goede ---- - 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.0 - -From 59872e5aa96479948da4b1d04a10101d8cb86da2 Mon Sep 17 00:00:00 2001 -From: Bingbu Cao -Date: Thu, 11 Jan 2024 14:55:27 +0800 -Subject: [PATCH 13/31] MAINTAINERS: add maintainers for Intel IPU6 input - system driver - -Update MAINTAINERS file for Intel IPU6 input system driver. - -Signed-off-by: Bingbu Cao -Link: https://lore.kernel.org/r/20240111065531.2418836-14-bingbu.cao@intel.com -Signed-off-by: Hans de Goede ---- - MAINTAINERS | 10 ++++++++++ - 1 file changed, 10 insertions(+) - -diff --git a/MAINTAINERS b/MAINTAINERS -index a7c4cf8201e0..bedffa6941ab 100644 ---- a/MAINTAINERS -+++ b/MAINTAINERS -@@ -10733,6 +10733,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 -+M: Bingbu Cao -+R: Tianshu Qiu -+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 - L: platform-driver-x86@vger.kernel.org --- -2.43.0 - -From 7acc1618d2f9cbb0d6b07ce9a1eace25663b9fb2 Mon Sep 17 00:00:00 2001 -From: Bingbu Cao -Date: Thu, 11 Jan 2024 14:55:28 +0800 -Subject: [PATCH 14/31] 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 -Link: https://lore.kernel.org/r/20240111065531.2418836-15-bingbu.cao@intel.com -Signed-off-by: Hans de Goede ---- - 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:: -+ -+======================================================== -+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 @@ -+ -+ -+ -+ -+ -+ -+board -+ -+ -+n00000001 -+ -+Intel IPU6 ISYS Capture 0 -+/dev/video0 -+ -+ -+n00000002 -+ -+Intel IPU6 ISYS Capture 1 -+/dev/video1 -+ -+ -+n00000003 -+ -+Intel IPU6 ISYS Capture 2 -+/dev/video2 -+ -+ -+n00000004 -+ -+Intel IPU6 ISYS Capture 3 -+/dev/video3 -+ -+ -+n0000007d -+ -+0 -+ -+Intel IPU6 CSI2 0 -+/dev/v4l-subdev0 -+ -+1 -+ -+2 -+ -+3 -+ -+4 -+ -+5 -+ -+6 -+ -+7 -+ -+8 -+ -+ -+n0000007d:port1->n00000001 -+ -+ -+ -+ -+n00000087 -+ -+0 -+ -+Intel IPU6 CSI2 1 -+/dev/v4l-subdev1 -+ -+1 -+ -+2 -+ -+3 -+ -+4 -+ -+5 -+ -+6 -+ -+7 -+ -+8 -+ -+ -+n00000087:port1->n00000002 -+ -+ -+ -+ -+n00000091 -+ -+0 -+ -+Intel IPU6 CSI2 2 -+/dev/v4l-subdev2 -+ -+1 -+ -+2 -+ -+3 -+ -+4 -+ -+5 -+ -+6 -+ -+7 -+ -+8 -+ -+ -+n00000091:port1->n00000003 -+ -+ -+ -+ -+n0000009b -+ -+0 -+ -+Intel IPU6 CSI2 3 -+/dev/v4l-subdev3 -+ -+1 -+ -+2 -+ -+3 -+ -+4 -+ -+5 -+ -+6 -+ -+7 -+ -+8 -+ -+ -+n0000009b:port1->n00000004 -+ -+ -+ -+ -+n00000865 -+ -+ -+ -+ov01a10 3-0036 -+/dev/v4l-subdev4 -+ -+0 -+ -+ -+n00000865:port0->n00000091:port0 -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -+ -diff --git a/Documentation/admin-guide/media/v4l-drivers.rst b/Documentation/admin-guide/media/v4l-drivers.rst -index 61283d67ceef..50bdef2d1762 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.0 - -From 878747fcd0bb4f27933d3d32525472b8a0425724 Mon Sep 17 00:00:00 2001 -From: Bingbu Cao -Date: Thu, 11 Jan 2024 14:55:29 +0800 -Subject: [PATCH 15/31] 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 -Link: https://lore.kernel.org/r/20240111065531.2418836-16-bingbu.cao@intel.com -Signed-off-by: Hans de Goede ---- - .../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 -+ -+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` about how to do -+ capture by IPU6 IS driver. -+ -+ --- -2.43.0 - -From 7103b8fabe3158d203b72036abc2a964687d46b8 Mon Sep 17 00:00:00 2001 -From: Hans de Goede -Date: Mon, 15 Jan 2024 15:57:06 +0100 -Subject: [PATCH 16/31] 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 ---- - .../media/pci/intel/ipu6/ipu6-isys-video.c | 60 +++++++++++++------ - 1 file changed, 43 insertions(+), 17 deletions(-) - -diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys-video.c b/drivers/media/pci/intel/ipu6/ipu6-isys-video.c -index 847eac26bcd6..16d15f05a6dc 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,14 +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}, - }; - - static int video_open(struct file *file) -@@ -109,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; - } -@@ -138,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; - -- if (f->index >= ARRAY_SIZE(ipu6_isys_pfmts)) -+ 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 >= 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.0 - -From ddd2826369e8ee4ba5b04502e1e20869590742da Mon Sep 17 00:00:00 2001 -From: Hans de Goede -Date: Mon, 6 Nov 2023 12:13:42 +0100 -Subject: [PATCH 17/31] media: Add ov01a1s driver - -Add ov01a1s driver from: -https://github.com/intel/ipu6-drivers/ - -Signed-off-by: Hans de Goede ---- - 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 59ee0ca2c978..d5a516e4fce2 100644 ---- a/drivers/media/i2c/Kconfig -+++ b/drivers/media/i2c/Kconfig -@@ -283,6 +283,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 f5010f80a21f..ed5aa45e3506 100644 ---- a/drivers/media/i2c/Makefile -+++ b/drivers/media/i2c/Makefile -@@ -73,6 +73,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 -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+#if IS_ENABLED(CONFIG_INTEL_SKL_INT3472) -+#include -+#include -+#elif IS_ENABLED(CONFIG_POWER_CTRL_LOGIC) -+#include "power_ctrl_logic.h" -+#endif -+#if IS_ENABLED(CONFIG_INTEL_VSC) -+#include -+#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 "); -+MODULE_AUTHOR("Lai, Jim "); -+MODULE_AUTHOR("Qiu, Tianshu "); -+MODULE_AUTHOR("Shawn Tu "); -+MODULE_AUTHOR("Bingbu Cao "); -+MODULE_DESCRIPTION("OmniVision OV01A1S sensor driver"); -+MODULE_LICENSE("GPL v2"); --- -2.43.0 - -From 07d707663a69f65c366d3ac75c2bf41749f33224 Mon Sep 17 00:00:00 2001 -From: Hans de Goede -Date: Mon, 6 Nov 2023 12:33:56 +0100 -Subject: [PATCH 18/31] media: ov01a1s: Remove non upstream iVSC support - -Signed-off-by: Hans de Goede ---- - 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 0dcce8b492b4..c97c1a661022 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 --#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; - } -@@ -1044,18 +985,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.0 - -From 3f59512af8a39b5d22694e2996d8441d5e723423 Mon Sep 17 00:00:00 2001 -From: Hans de Goede -Date: Mon, 4 Dec 2023 13:39:38 +0100 -Subject: [PATCH 19/31] media: ov2740: 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 ThinkPad models with -IPU6 + ov2740 sensor, the sensor driver must control the reset GPIO -and the sensor's clock itself. - -Add support for having the driver control an optional reset GPIO. - -Reviewed-by: Bingbu Cao -Signed-off-by: Hans de Goede -Signed-off-by: Sakari Ailus -Signed-off-by: Hans Verkuil ---- - drivers/media/i2c/ov2740.c | 48 ++++++++++++++++++++++++++++++++++++-- - 1 file changed, 46 insertions(+), 2 deletions(-) - -diff --git a/drivers/media/i2c/ov2740.c b/drivers/media/i2c/ov2740.c -index 24e468485fbf..e5f9569a229d 100644 ---- a/drivers/media/i2c/ov2740.c -+++ b/drivers/media/i2c/ov2740.c -@@ -4,6 +4,7 @@ - #include - #include - #include -+#include - #include - #include - #include -@@ -333,6 +334,9 @@ struct ov2740 { - struct v4l2_ctrl *hblank; - struct v4l2_ctrl *exposure; - -+ /* GPIOs, clocks */ -+ struct gpio_desc *reset_gpio; -+ - /* Current mode */ - const struct ov2740_mode *cur_mode; - -@@ -1058,6 +1062,26 @@ static int ov2740_register_nvmem(struct i2c_client *client, - return 0; - } - -+static int ov2740_suspend(struct device *dev) -+{ -+ struct v4l2_subdev *sd = dev_get_drvdata(dev); -+ struct ov2740 *ov2740 = to_ov2740(sd); -+ -+ gpiod_set_value_cansleep(ov2740->reset_gpio, 1); -+ return 0; -+} -+ -+static int ov2740_resume(struct device *dev) -+{ -+ struct v4l2_subdev *sd = dev_get_drvdata(dev); -+ struct ov2740 *ov2740 = to_ov2740(sd); -+ -+ gpiod_set_value_cansleep(ov2740->reset_gpio, 0); -+ msleep(20); -+ -+ return 0; -+} -+ - static int ov2740_probe(struct i2c_client *client) - { - struct device *dev = &client->dev; -@@ -1073,12 +1097,24 @@ static int ov2740_probe(struct i2c_client *client) - if (!ov2740) - return -ENOMEM; - -+ ov2740->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH); -+ if (IS_ERR(ov2740->reset_gpio)) -+ return dev_err_probe(dev, PTR_ERR(ov2740->reset_gpio), -+ "failed to get reset GPIO\n"); -+ - v4l2_i2c_subdev_init(&ov2740->sd, client, &ov2740_subdev_ops); - full_power = acpi_dev_state_d0(&client->dev); - if (full_power) { -- ret = ov2740_identify_module(ov2740); -+ /* ACPI does not always clear the reset GPIO / enable the clock */ -+ ret = ov2740_resume(dev); - if (ret) -- return dev_err_probe(dev, ret, "failed to find sensor\n"); -+ return dev_err_probe(dev, ret, "failed to power on sensor\n"); -+ -+ ret = ov2740_identify_module(ov2740); -+ if (ret) { -+ dev_err_probe(dev, ret, "failed to find sensor\n"); -+ goto probe_error_power_off; -+ } - } - - ov2740->cur_mode = &supported_modes[0]; -@@ -1132,9 +1168,16 @@ static int ov2740_probe(struct i2c_client *client) - probe_error_v4l2_ctrl_handler_free: - v4l2_ctrl_handler_free(ov2740->sd.ctrl_handler); - -+probe_error_power_off: -+ if (full_power) -+ ov2740_suspend(dev); -+ - return ret; - } - -+static DEFINE_RUNTIME_DEV_PM_OPS(ov2740_pm_ops, ov2740_suspend, ov2740_resume, -+ NULL); -+ - static const struct acpi_device_id ov2740_acpi_ids[] = { - {"INT3474"}, - {} -@@ -1146,6 +1189,7 @@ static struct i2c_driver ov2740_i2c_driver = { - .driver = { - .name = "ov2740", - .acpi_match_table = ov2740_acpi_ids, -+ .pm = pm_sleep_ptr(&ov2740_pm_ops), - }, - .probe = ov2740_probe, - .remove = ov2740_remove, --- -2.43.0 - -From ddde5ef9b19c4e4755be62c588209db986e57400 Mon Sep 17 00:00:00 2001 -From: Hans de Goede -Date: Mon, 4 Dec 2023 13:39:39 +0100 -Subject: [PATCH 20/31] media: ov2740: 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 ThinkPad models with -IPU6 + ov2740 sensor, the sensor driver must control the reset GPIO -and the sensor's clock itself. - -Add support for having the driver control an optional clock. - -Reviewed-by: Bingbu Cao -Signed-off-by: Hans de Goede -Signed-off-by: Sakari Ailus -Signed-off-by: Hans Verkuil ---- - drivers/media/i2c/ov2740.c | 13 +++++++++++++ - 1 file changed, 13 insertions(+) - -diff --git a/drivers/media/i2c/ov2740.c b/drivers/media/i2c/ov2740.c -index e5f9569a229d..0396e40419ca 100644 ---- a/drivers/media/i2c/ov2740.c -+++ b/drivers/media/i2c/ov2740.c -@@ -3,6 +3,7 @@ - - #include - #include -+#include - #include - #include - #include -@@ -336,6 +337,7 @@ struct ov2740 { - - /* GPIOs, clocks */ - struct gpio_desc *reset_gpio; -+ struct clk *clk; - - /* Current mode */ - const struct ov2740_mode *cur_mode; -@@ -1068,6 +1070,7 @@ static int ov2740_suspend(struct device *dev) - struct ov2740 *ov2740 = to_ov2740(sd); - - gpiod_set_value_cansleep(ov2740->reset_gpio, 1); -+ clk_disable_unprepare(ov2740->clk); - return 0; - } - -@@ -1075,6 +1078,11 @@ static int ov2740_resume(struct device *dev) - { - struct v4l2_subdev *sd = dev_get_drvdata(dev); - struct ov2740 *ov2740 = to_ov2740(sd); -+ int ret; -+ -+ ret = clk_prepare_enable(ov2740->clk); -+ if (ret) -+ return ret; - - gpiod_set_value_cansleep(ov2740->reset_gpio, 0); - msleep(20); -@@ -1102,6 +1110,11 @@ static int ov2740_probe(struct i2c_client *client) - return dev_err_probe(dev, PTR_ERR(ov2740->reset_gpio), - "failed to get reset GPIO\n"); - -+ ov2740->clk = devm_clk_get_optional(dev, "clk"); -+ if (IS_ERR(ov2740->clk)) -+ return dev_err_probe(dev, PTR_ERR(ov2740->clk), -+ "failed to get clock\n"); -+ - v4l2_i2c_subdev_init(&ov2740->sd, client, &ov2740_subdev_ops); - full_power = acpi_dev_state_d0(&client->dev); - if (full_power) { --- -2.43.0 - -From be361f059239a5444a436a73888cc1c1665d90a7 Mon Sep 17 00:00:00 2001 -From: Hans de Goede -Date: Mon, 4 Dec 2023 13:39:40 +0100 -Subject: [PATCH 21/31] media: ov2740: Move fwnode_graph_get_next_endpoint() - call up - -If the bridge has not yet setup the fwnode-graph then -the fwnode_property_read_u32("clock-frequency") call will fail. - -Move the fwnode_graph_get_next_endpoint() call to above reading -the clock-frequency. - -Signed-off-by: Hans de Goede -Signed-off-by: Sakari Ailus -Signed-off-by: Hans Verkuil ---- - drivers/media/i2c/ov2740.c | 26 +++++++++++++++++--------- - 1 file changed, 17 insertions(+), 9 deletions(-) - -diff --git a/drivers/media/i2c/ov2740.c b/drivers/media/i2c/ov2740.c -index 0396e40419ca..35b2f43bd3e5 100644 ---- a/drivers/media/i2c/ov2740.c -+++ b/drivers/media/i2c/ov2740.c -@@ -926,19 +926,27 @@ static int ov2740_check_hwcfg(struct device *dev) - int ret; - unsigned int i, j; - -- ret = fwnode_property_read_u32(fwnode, "clock-frequency", &mclk); -- if (ret) -- return ret; -- -- if (mclk != OV2740_MCLK) -- return dev_err_probe(dev, -EINVAL, -- "external clock %d is not supported\n", -- mclk); -- -+ /* -+ * 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) { -+ fwnode_handle_put(ep); -+ return ret; -+ } -+ -+ if (mclk != OV2740_MCLK) { -+ fwnode_handle_put(ep); -+ return dev_err_probe(dev, -EINVAL, -+ "external clock %d is not supported\n", -+ mclk); -+ } -+ - ret = v4l2_fwnode_endpoint_alloc_parse(ep, &bus_cfg); - fwnode_handle_put(ep); - if (ret) --- -2.43.0 - -From 4d499411eccc2db42d0d21365039f479c9367214 Mon Sep 17 00:00:00 2001 -From: Hans de Goede -Date: Mon, 4 Dec 2023 13:39:41 +0100 -Subject: [PATCH 22/31] media: ov2740: Improve ov2740_check_hwcfg() error - reporting - -Make ov2740_check_hwcfg() report an error on failure in all error paths, -so that it is always clear why the probe() failed. - -Signed-off-by: Hans de Goede -Signed-off-by: Sakari Ailus -Signed-off-by: Hans Verkuil ---- - drivers/media/i2c/ov2740.c | 5 +++-- - 1 file changed, 3 insertions(+), 2 deletions(-) - -diff --git a/drivers/media/i2c/ov2740.c b/drivers/media/i2c/ov2740.c -index 35b2f43bd3e5..87176948f766 100644 ---- a/drivers/media/i2c/ov2740.c -+++ b/drivers/media/i2c/ov2740.c -@@ -937,7 +937,8 @@ static int ov2740_check_hwcfg(struct device *dev) - ret = fwnode_property_read_u32(fwnode, "clock-frequency", &mclk); - if (ret) { - fwnode_handle_put(ep); -- return ret; -+ return dev_err_probe(dev, ret, -+ "reading clock-frequency property\n"); - } - - if (mclk != OV2740_MCLK) { -@@ -950,7 +951,7 @@ static int ov2740_check_hwcfg(struct device *dev) - ret = v4l2_fwnode_endpoint_alloc_parse(ep, &bus_cfg); - fwnode_handle_put(ep); - if (ret) -- return ret; -+ return dev_err_probe(dev, ret, "parsing endpoint failed\n"); - - if (bus_cfg.bus.mipi_csi2.num_data_lanes != OV2740_DATA_LANES) { - ret = dev_err_probe(dev, -EINVAL, --- -2.43.0 - -From 28c5209404274ded2f2fb2c93611da32e03a2538 Mon Sep 17 00:00:00 2001 -From: Hans de Goede -Date: Mon, 4 Dec 2023 13:39:43 +0100 -Subject: [PATCH 24/31] media: ov2740: Check hwcfg after allocating the ov2740 - struct - -Alloc ov2740_data and set up the drvdata pointer before calling -ov2740_check_hwcfg(). - -This is a preparation patch to allow ov2740_check_hwcfg() -to store some of the parsed data in the ov2740 struct. - -Signed-off-by: Hans de Goede -Signed-off-by: Sakari Ailus -Signed-off-by: Hans Verkuil ---- - drivers/media/i2c/ov2740.c | 11 ++++++----- - 1 file changed, 6 insertions(+), 5 deletions(-) - -diff --git a/drivers/media/i2c/ov2740.c b/drivers/media/i2c/ov2740.c -index a646be427ab2..28f4659a6bfb 100644 ---- a/drivers/media/i2c/ov2740.c -+++ b/drivers/media/i2c/ov2740.c -@@ -1095,14 +1095,16 @@ static int ov2740_probe(struct i2c_client *client) - bool full_power; - int ret; - -- ret = ov2740_check_hwcfg(&client->dev); -- if (ret) -- return dev_err_probe(dev, ret, "failed to check HW configuration\n"); -- - ov2740 = devm_kzalloc(&client->dev, sizeof(*ov2740), GFP_KERNEL); - if (!ov2740) - return -ENOMEM; - -+ v4l2_i2c_subdev_init(&ov2740->sd, client, &ov2740_subdev_ops); -+ -+ ret = ov2740_check_hwcfg(dev); -+ if (ret) -+ return dev_err_probe(dev, ret, "failed to check HW configuration\n"); -+ - ov2740->reset_gpio = devm_gpiod_get_optional(dev, "reset", GPIOD_OUT_HIGH); - if (IS_ERR(ov2740->reset_gpio)) - return dev_err_probe(dev, PTR_ERR(ov2740->reset_gpio), -@@ -1113,7 +1115,6 @@ static int ov2740_probe(struct i2c_client *client) - return dev_err_probe(dev, PTR_ERR(ov2740->clk), - "failed to get clock\n"); - -- v4l2_i2c_subdev_init(&ov2740->sd, client, &ov2740_subdev_ops); - full_power = acpi_dev_state_d0(&client->dev); - if (full_power) { - /* ACPI does not always clear the reset GPIO / enable the clock */ --- -2.43.0 - -From 5e06f2a1cbbaf0ceffd62475ad4170f7224372be Mon Sep 17 00:00:00 2001 -From: Hans de Goede -Date: Mon, 4 Dec 2023 13:39:44 +0100 -Subject: [PATCH 25/31] media: ov2740: Add support for 180 MHz link frequency - -On various Lenovo Thinkpad models with an ov2740 sensor the 360 MHz -link frequency is not supported. - -Add support for 180 MHz link frequency, even though this has half the -pixel clock, this supports the same framerate by using half the VTS value -(significantly reducing the amount of empty lines send during vblank). - -Normally if there are multiple link-frequencies then the sensor driver -choses the lowest link-frequency still usable for the chosen resolution. - -In this case the board supports only 1 link-frequency. Which frequency -is supported is checked in ov2740_check_hwcfg() and then a different -set of supported_modes (using only the supported link-freq) is selected. - -The register settings for this were taken from the ov2740 sensor driver -in the out of tree IPU6 driver: - -https://github.com/intel/ipu6-drivers/ - -Signed-off-by: Hans de Goede -Signed-off-by: Sakari Ailus -Signed-off-by: Hans Verkuil ---- - drivers/media/i2c/ov2740.c | 263 +++++++++++++++++++++++++++++++++---- - 1 file changed, 239 insertions(+), 24 deletions(-) - -diff --git a/drivers/media/i2c/ov2740.c b/drivers/media/i2c/ov2740.c -index 28f4659a6bfb..85629992d3aa 100644 ---- a/drivers/media/i2c/ov2740.c -+++ b/drivers/media/i2c/ov2740.c -@@ -16,6 +16,7 @@ - #include - - #define OV2740_LINK_FREQ_360MHZ 360000000ULL -+#define OV2740_LINK_FREQ_180MHZ 180000000ULL - #define OV2740_SCLK 72000000LL - #define OV2740_MCLK 19200000 - #define OV2740_DATA_LANES 2 -@@ -30,9 +31,6 @@ - - /* vertical-timings from sensor */ - #define OV2740_REG_VTS 0x380e --#define OV2740_VTS_DEF 0x088a --#define OV2740_VTS_MIN 0x0460 --#define OV2740_VTS_MAX 0x7fff - - /* horizontal-timings from sensor */ - #define OV2740_REG_HTS 0x380c -@@ -86,6 +84,7 @@ struct nvm_data { - - enum { - OV2740_LINK_FREQ_360MHZ_INDEX, -+ OV2740_LINK_FREQ_180MHZ_INDEX, - }; - - struct ov2740_reg { -@@ -118,6 +117,9 @@ struct ov2740_mode { - /* Min vertical timining size */ - u32 vts_min; - -+ /* Max vertical timining size */ -+ u32 vts_max; -+ - /* Link frequency needed for this resolution */ - u32 link_freq_index; - -@@ -134,7 +136,18 @@ static const struct ov2740_reg mipi_data_rate_720mbps[] = { - {0x0312, 0x11}, - }; - --static const struct ov2740_reg mode_1932x1092_regs[] = { -+static const struct ov2740_reg mipi_data_rate_360mbps[] = { -+ {0x0103, 0x01}, -+ {0x0302, 0x4b}, -+ {0x0303, 0x01}, -+ {0x030d, 0x4b}, -+ {0x030e, 0x02}, -+ {0x030a, 0x01}, -+ {0x0312, 0x11}, -+ {0x4837, 0x2c}, -+}; -+ -+static const struct ov2740_reg mode_1932x1092_regs_360mhz[] = { - {0x3000, 0x00}, - {0x3018, 0x32}, - {0x3031, 0x0a}, -@@ -287,6 +300,159 @@ static const struct ov2740_reg mode_1932x1092_regs[] = { - {0x3813, 0x01}, - }; - -+static const struct ov2740_reg mode_1932x1092_regs_180mhz[] = { -+ {0x3000, 0x00}, -+ {0x3018, 0x32}, /* 0x32 for 2 lanes, 0x12 for 1 lane */ -+ {0x3031, 0x0a}, -+ {0x3080, 0x08}, -+ {0x3083, 0xB4}, -+ {0x3103, 0x00}, -+ {0x3104, 0x01}, -+ {0x3106, 0x01}, -+ {0x3500, 0x00}, -+ {0x3501, 0x44}, -+ {0x3502, 0x40}, -+ {0x3503, 0x88}, -+ {0x3507, 0x00}, -+ {0x3508, 0x00}, -+ {0x3509, 0x80}, -+ {0x350c, 0x00}, -+ {0x350d, 0x80}, -+ {0x3510, 0x00}, -+ {0x3511, 0x00}, -+ {0x3512, 0x20}, -+ {0x3632, 0x00}, -+ {0x3633, 0x10}, -+ {0x3634, 0x10}, -+ {0x3635, 0x10}, -+ {0x3645, 0x13}, -+ {0x3646, 0x81}, -+ {0x3636, 0x10}, -+ {0x3651, 0x0a}, -+ {0x3656, 0x02}, -+ {0x3659, 0x04}, -+ {0x365a, 0xda}, -+ {0x365b, 0xa2}, -+ {0x365c, 0x04}, -+ {0x365d, 0x1d}, -+ {0x365e, 0x1a}, -+ {0x3662, 0xd7}, -+ {0x3667, 0x78}, -+ {0x3669, 0x0a}, -+ {0x366a, 0x92}, -+ {0x3700, 0x54}, -+ {0x3702, 0x10}, -+ {0x3706, 0x42}, -+ {0x3709, 0x30}, -+ {0x370b, 0xc2}, -+ {0x3714, 0x63}, -+ {0x3715, 0x01}, -+ {0x3716, 0x00}, -+ {0x371a, 0x3e}, -+ {0x3732, 0x0e}, -+ {0x3733, 0x10}, -+ {0x375f, 0x0e}, -+ {0x3768, 0x30}, -+ {0x3769, 0x44}, -+ {0x376a, 0x22}, -+ {0x377b, 0x20}, -+ {0x377c, 0x00}, -+ {0x377d, 0x0c}, -+ {0x3798, 0x00}, -+ {0x37a1, 0x55}, -+ {0x37a8, 0x6d}, -+ {0x37c2, 0x04}, -+ {0x37c5, 0x00}, -+ {0x37c8, 0x00}, -+ {0x3800, 0x00}, -+ {0x3801, 0x00}, -+ {0x3802, 0x00}, -+ {0x3803, 0x00}, -+ {0x3804, 0x07}, -+ {0x3805, 0x8f}, -+ {0x3806, 0x04}, -+ {0x3807, 0x47}, -+ {0x3808, 0x07}, -+ {0x3809, 0x88}, -+ {0x380a, 0x04}, -+ {0x380b, 0x40}, -+ {0x380c, 0x08}, -+ {0x380d, 0x70}, -+ {0x380e, 0x04}, -+ {0x380f, 0x56}, -+ {0x3810, 0x00}, -+ {0x3811, 0x04}, -+ {0x3812, 0x00}, -+ {0x3813, 0x04}, -+ {0x3814, 0x01}, -+ {0x3815, 0x01}, -+ {0x3820, 0x80}, -+ {0x3821, 0x46}, -+ {0x3822, 0x84}, -+ {0x3829, 0x00}, -+ {0x382a, 0x01}, -+ {0x382b, 0x01}, -+ {0x3830, 0x04}, -+ {0x3836, 0x01}, -+ {0x3837, 0x08}, -+ {0x3839, 0x01}, -+ {0x383a, 0x00}, -+ {0x383b, 0x08}, -+ {0x383c, 0x00}, -+ {0x3f0b, 0x00}, -+ {0x4001, 0x20}, -+ {0x4009, 0x07}, -+ {0x4003, 0x10}, -+ {0x4010, 0xe0}, -+ {0x4016, 0x00}, -+ {0x4017, 0x10}, -+ {0x4044, 0x02}, -+ {0x4304, 0x08}, -+ {0x4307, 0x30}, -+ {0x4320, 0x80}, -+ {0x4322, 0x00}, -+ {0x4323, 0x00}, -+ {0x4324, 0x00}, -+ {0x4325, 0x00}, -+ {0x4326, 0x00}, -+ {0x4327, 0x00}, -+ {0x4328, 0x00}, -+ {0x4329, 0x00}, -+ {0x432c, 0x03}, -+ {0x432d, 0x81}, -+ {0x4501, 0x84}, -+ {0x4502, 0x40}, -+ {0x4503, 0x18}, -+ {0x4504, 0x04}, -+ {0x4508, 0x02}, -+ {0x4601, 0x10}, -+ {0x4800, 0x00}, -+ {0x4816, 0x52}, -+ {0x5000, 0x73}, /* 0x7f enable DPC */ -+ {0x5001, 0x00}, -+ {0x5005, 0x38}, -+ {0x501e, 0x0d}, -+ {0x5040, 0x00}, -+ {0x5901, 0x00}, -+ {0x3800, 0x00}, -+ {0x3801, 0x00}, -+ {0x3802, 0x00}, -+ {0x3803, 0x00}, -+ {0x3804, 0x07}, -+ {0x3805, 0x8f}, -+ {0x3806, 0x04}, -+ {0x3807, 0x47}, -+ {0x3808, 0x07}, -+ {0x3809, 0x8c}, -+ {0x380a, 0x04}, -+ {0x380b, 0x44}, -+ {0x3810, 0x00}, -+ {0x3811, 0x00}, -+ {0x3812, 0x00}, -+ {0x3813, 0x01}, -+ {0x4003, 0x40}, /* set Black level to 0x40 */ -+}; -+ - static const char * const ov2740_test_pattern_menu[] = { - "Disabled", - "Color Bar", -@@ -297,6 +463,7 @@ static const char * const ov2740_test_pattern_menu[] = { - - static const s64 link_freq_menu_items[] = { - OV2740_LINK_FREQ_360MHZ, -+ OV2740_LINK_FREQ_180MHZ, - }; - - static const struct ov2740_link_freq_config link_freq_configs[] = { -@@ -306,23 +473,46 @@ static const struct ov2740_link_freq_config link_freq_configs[] = { - .regs = mipi_data_rate_720mbps, - } - }, -+ [OV2740_LINK_FREQ_180MHZ_INDEX] = { -+ .reg_list = { -+ .num_of_regs = ARRAY_SIZE(mipi_data_rate_360mbps), -+ .regs = mipi_data_rate_360mbps, -+ } -+ }, - }; - --static const struct ov2740_mode supported_modes[] = { -+static const struct ov2740_mode supported_modes_360mhz[] = { - { - .width = 1932, - .height = 1092, - .hts = 2160, -- .vts_def = OV2740_VTS_DEF, -- .vts_min = OV2740_VTS_MIN, -+ .vts_min = 1120, -+ .vts_def = 2186, -+ .vts_max = 32767, - .reg_list = { -- .num_of_regs = ARRAY_SIZE(mode_1932x1092_regs), -- .regs = mode_1932x1092_regs, -+ .num_of_regs = ARRAY_SIZE(mode_1932x1092_regs_360mhz), -+ .regs = mode_1932x1092_regs_360mhz, - }, - .link_freq_index = OV2740_LINK_FREQ_360MHZ_INDEX, - }, - }; - -+static const struct ov2740_mode supported_modes_180mhz[] = { -+ { -+ .width = 1932, -+ .height = 1092, -+ .hts = 2160, -+ .vts_min = 1110, -+ .vts_def = 1110, -+ .vts_max = 2047, -+ .reg_list = { -+ .num_of_regs = ARRAY_SIZE(mode_1932x1092_regs_180mhz), -+ .regs = mode_1932x1092_regs_180mhz, -+ }, -+ .link_freq_index = OV2740_LINK_FREQ_180MHZ_INDEX, -+ }, -+}; -+ - struct ov2740 { - struct v4l2_subdev sd; - struct media_pad pad; -@@ -345,6 +535,10 @@ struct ov2740 { - /* NVM data inforamtion */ - struct nvm_data *nvm; - -+ /* Supported modes */ -+ const struct ov2740_mode *supported_modes; -+ int supported_modes_count; -+ - /* True if the device has been identified */ - bool identified; - }; -@@ -589,7 +783,7 @@ static int ov2740_init_controls(struct ov2740 *ov2740) - pixel_rate, 1, pixel_rate); - - vblank_min = cur_mode->vts_min - cur_mode->height; -- vblank_max = OV2740_VTS_MAX - cur_mode->height; -+ vblank_max = cur_mode->vts_max - cur_mode->height; - vblank_default = cur_mode->vts_def - cur_mode->height; - ov2740->vblank = v4l2_ctrl_new_std(ctrl_hdlr, &ov2740_ctrl_ops, - V4L2_CID_VBLANK, vblank_min, -@@ -816,10 +1010,10 @@ static int ov2740_set_format(struct v4l2_subdev *sd, - const struct ov2740_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); -+ mode = v4l2_find_nearest_size(ov2740->supported_modes, -+ ov2740->supported_modes_count, -+ width, height, -+ fmt->format.width, fmt->format.height); - - ov2740_update_pad_format(mode, &fmt->format); - *v4l2_subdev_get_pad_format(sd, sd_state, fmt->pad) = fmt->format; -@@ -836,7 +1030,7 @@ static int ov2740_set_format(struct v4l2_subdev *sd, - vblank_def = mode->vts_def - mode->height; - __v4l2_ctrl_modify_range(ov2740->vblank, - mode->vts_min - mode->height, -- OV2740_VTS_MAX - mode->height, 1, vblank_def); -+ mode->vts_max - mode->height, 1, vblank_def); - __v4l2_ctrl_s_ctrl(ov2740->vblank, vblank_def); - h_blank = mode->hts - mode->width; - __v4l2_ctrl_modify_range(ov2740->hblank, h_blank, h_blank, 1, h_blank); -@@ -860,7 +1054,10 @@ static int ov2740_enum_frame_size(struct v4l2_subdev *sd, - struct v4l2_subdev_state *sd_state, - struct v4l2_subdev_frame_size_enum *fse) - { -- if (fse->index >= ARRAY_SIZE(supported_modes)) -+ struct ov2740 *ov2740 = to_ov2740(sd); -+ const struct ov2740_mode *supported_modes = ov2740->supported_modes; -+ -+ if (fse->index >= ov2740->supported_modes_count) - return -EINVAL; - - if (fse->code != MEDIA_BUS_FMT_SGRBG10_1X10) -@@ -877,9 +1074,10 @@ static int ov2740_enum_frame_size(struct v4l2_subdev *sd, - static int ov2740_init_cfg(struct v4l2_subdev *sd, - struct v4l2_subdev_state *sd_state) - { -- ov2740_update_pad_format(&supported_modes[0], -- v4l2_subdev_get_pad_format(sd, sd_state, 0)); -+ struct ov2740 *ov2740 = to_ov2740(sd); - -+ ov2740_update_pad_format(&ov2740->supported_modes[0], -+ v4l2_subdev_get_pad_format(sd, sd_state, 0)); - return 0; - } - -@@ -906,6 +1104,8 @@ static const struct media_entity_operations ov2740_subdev_entity_ops = { - - static int ov2740_check_hwcfg(struct device *dev) - { -+ struct v4l2_subdev *sd = dev_get_drvdata(dev); -+ struct ov2740 *ov2740 = to_ov2740(sd); - struct fwnode_handle *ep; - struct fwnode_handle *fwnode = dev_fwnode(dev); - struct v4l2_fwnode_endpoint bus_cfg = { -@@ -961,14 +1161,29 @@ static int ov2740_check_hwcfg(struct device *dev) - break; - } - -- if (j == bus_cfg.nr_of_link_frequencies) { -- ret = dev_err_probe(dev, -EINVAL, -- "no link frequency %lld supported\n", -- link_freq_menu_items[i]); -- goto check_hwcfg_error; -+ if (j == bus_cfg.nr_of_link_frequencies) -+ continue; -+ -+ switch (i) { -+ case OV2740_LINK_FREQ_360MHZ_INDEX: -+ ov2740->supported_modes = supported_modes_360mhz; -+ ov2740->supported_modes_count = -+ ARRAY_SIZE(supported_modes_360mhz); -+ break; -+ case OV2740_LINK_FREQ_180MHZ_INDEX: -+ ov2740->supported_modes = supported_modes_180mhz; -+ ov2740->supported_modes_count = -+ ARRAY_SIZE(supported_modes_180mhz); -+ break; - } -+ -+ break; /* Prefer modes from first available link-freq */ - } - -+ if (!ov2740->supported_modes) -+ ret = dev_err_probe(dev, -EINVAL, -+ "no supported link frequencies\n"); -+ - check_hwcfg_error: - v4l2_fwnode_endpoint_free(&bus_cfg); - -@@ -1129,7 +1344,7 @@ static int ov2740_probe(struct i2c_client *client) - } - } - -- ov2740->cur_mode = &supported_modes[0]; -+ ov2740->cur_mode = &ov2740->supported_modes[0]; - ret = ov2740_init_controls(ov2740); - if (ret) { - dev_err_probe(dev, ret, "failed to init controls\n"); --- -2.43.0 - -From b8c88a81135741a39f56d63673bfd2d2f5f12b3a Mon Sep 17 00:00:00 2001 -From: Hans de Goede -Date: Mon, 4 Dec 2023 13:39:45 +0100 -Subject: [PATCH 26/31] media: ov2740: Add a sleep after resetting the sensor - -Split the resetting of the sensor out of the link_freq_config reg_list -and add a delay after this. - -This hopefully fixes the stream sometimes not starting, this was -taken from the ov2740 sensor driver in the out of tree IPU6 driver: - -https://github.com/intel/ipu6-drivers/ - -Signed-off-by: Hans de Goede -Signed-off-by: Sakari Ailus -Signed-off-by: Hans Verkuil ---- - drivers/media/i2c/ov2740.c | 11 +++++++++-- - 1 file changed, 9 insertions(+), 2 deletions(-) - -diff --git a/drivers/media/i2c/ov2740.c b/drivers/media/i2c/ov2740.c -index 85629992d3aa..9666f6e293d9 100644 ---- a/drivers/media/i2c/ov2740.c -+++ b/drivers/media/i2c/ov2740.c -@@ -128,7 +128,6 @@ struct ov2740_mode { - }; - - static const struct ov2740_reg mipi_data_rate_720mbps[] = { -- {0x0103, 0x01}, - {0x0302, 0x4b}, - {0x030d, 0x4b}, - {0x030e, 0x02}, -@@ -137,7 +136,6 @@ static const struct ov2740_reg mipi_data_rate_720mbps[] = { - }; - - static const struct ov2740_reg mipi_data_rate_360mbps[] = { -- {0x0103, 0x01}, - {0x0302, 0x4b}, - {0x0303, 0x01}, - {0x030d, 0x4b}, -@@ -935,6 +933,15 @@ static int ov2740_start_streaming(struct ov2740 *ov2740) - if (ov2740->nvm) - ov2740_load_otp_data(ov2740->nvm); - -+ /* Reset the sensor */ -+ ret = ov2740_write_reg(ov2740, 0x0103, 1, 0x01); -+ if (ret) { -+ dev_err(&client->dev, "failed to reset\n"); -+ return ret; -+ } -+ -+ usleep_range(10000, 15000); -+ - link_freq_index = ov2740->cur_mode->link_freq_index; - reg_list = &link_freq_configs[link_freq_index].reg_list; - ret = ov2740_write_reg_list(ov2740, reg_list); --- -2.43.0 - -From e807d266be092024e9963ffb6c7b9ace1153ec15 Mon Sep 17 00:00:00 2001 -From: Hans de Goede -Date: Mon, 4 Dec 2023 13:39:46 +0100 -Subject: [PATCH 27/31] media: ipu-bridge: Change ov2740 link-frequency to 180 - MHz - -The only known devices that use an ov2740 sensor in combination with -the ipu-bridge code are various Lenovo ThinkPad models, which all -need the link-frequency to be 180 MHz for things to work properly. - -The ov2740 driver used to only support 360 MHz link-frequency, -which is why the ipu-bridge entry used 360 MHz, but now the -ov2740 driver has been extended to also support 180 MHz. - -The ov2740 is actually used with 360 MHz link-frequency on Chromebooks. -On Chromebooks the camera/sensor fwnode graph is part of the ACPI tables. -The ipu-bridge code is used to dynamically generate the graph when it is -missing, so it is not used on Chromebooks and the ov2740 will keep using -360 MHz link-frequency there as before. - -Signed-off-by: Hans de Goede -Signed-off-by: Sakari Ailus -Signed-off-by: Hans Verkuil ---- - drivers/media/pci/intel/ipu-bridge.c | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/drivers/media/pci/intel/ipu-bridge.c b/drivers/media/pci/intel/ipu-bridge.c -index e38198e259c0..f980e3125a7b 100644 ---- a/drivers/media/pci/intel/ipu-bridge.c -+++ b/drivers/media/pci/intel/ipu-bridge.c -@@ -53,7 +53,7 @@ static const struct ipu_sensor_config ipu_supported_sensors[] = { - /* Omnivision ov8856 */ - IPU_SENSOR_CONFIG("OVTI8856", 3, 180000000, 360000000, 720000000), - /* Omnivision ov2740 */ -- IPU_SENSOR_CONFIG("INT3474", 1, 360000000), -+ IPU_SENSOR_CONFIG("INT3474", 1, 180000000), - /* Hynix hi556 */ - IPU_SENSOR_CONFIG("INT3537", 1, 437000000), - /* Omnivision ov13b10 */ --- -2.43.0 - -From d1c04b1284f0ce4d32ea73f6a325e08b31b2a323 Mon Sep 17 00:00:00 2001 -From: Hans de Goede -Date: Tue, 23 Jan 2024 14:58:35 +0100 -Subject: [PATCH 28/31] 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 ---- - 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 f6ea9b7b9700..258614b33f51 100644 ---- a/drivers/media/i2c/hi556.c -+++ b/drivers/media/i2c/hi556.c -@@ -1207,8 +1207,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) { -@@ -1221,10 +1226,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.0 - -From 109e64438f05a0cd791e5d84084672b2fcf12cf2 Mon Sep 17 00:00:00 2001 -From: Hans de Goede -Date: Tue, 23 Jan 2024 14:48:26 +0100 -Subject: [PATCH 29/31] 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 ---- - 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 258614b33f51..7398391989ea 100644 ---- a/drivers/media/i2c/hi556.c -+++ b/drivers/media/i2c/hi556.c -@@ -4,6 +4,7 @@ - #include - #include - #include -+#include - #include - #include - #include -@@ -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; - -@@ -1277,6 +1281,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; -@@ -1296,12 +1319,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; - } - } - -@@ -1346,9 +1381,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"}, -@@ -1362,6 +1404,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.0 - -From 23a74772614fcba8ad2ed9370db613ab0540072e Mon Sep 17 00:00:00 2001 -From: Hans de Goede -Date: Tue, 23 Jan 2024 14:54:22 +0100 -Subject: [PATCH 30/31] 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 ---- - 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 7398391989ea..fb6ba6984e38 100644 ---- a/drivers/media/i2c/hi556.c -+++ b/drivers/media/i2c/hi556.c -@@ -3,6 +3,7 @@ - - #include - #include -+#include - #include - #include - #include -@@ -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; -@@ -1287,6 +1289,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; - } - -@@ -1294,6 +1297,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); -@@ -1325,6 +1333,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.0 - -From 6c3b454ee3ab07dd2e10011d685f6bd9f03bee71 Mon Sep 17 00:00:00 2001 -From: Hans de Goede -Date: Wed, 24 Jan 2024 18:45:02 +0100 -Subject: [PATCH 31/31] 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 ---- - 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 fb6ba6984e38..90eff282a6e2 100644 ---- a/drivers/media/i2c/hi556.c -+++ b/drivers/media/i2c/hi556.c -@@ -9,6 +9,7 @@ - #include - #include - #include -+#include - #include - #include - #include -@@ -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; -@@ -1287,8 +1289,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; - } -@@ -1303,6 +1314,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; -@@ -1338,6 +1356,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.0 - diff --git a/modules/nixos/ipu6-softisp/libcamera/0001-libcamera-pipeline-simple-fix-size-adjustment-in-val.patch b/modules/nixos/ipu6-softisp/libcamera/0001-libcamera-pipeline-simple-fix-size-adjustment-in-val.patch deleted file mode 100644 index 7a71ed1..0000000 --- a/modules/nixos/ipu6-softisp/libcamera/0001-libcamera-pipeline-simple-fix-size-adjustment-in-val.patch +++ /dev/null @@ -1,43 +0,0 @@ -From aa818f7b749122f916be1ced48d1a3a2b3aeb47e Mon Sep 17 00:00:00 2001 -From: Andrey Konovalov -Date: Tue, 2 Jan 2024 23:47:20 +0300 -Subject: [PATCH 01/25] libcamera: pipeline: simple: fix size adjustment in - validate() - -SimpleCameraConfiguration::validate() adjusts the configuration -of its streams (if the size is not in the outputSizes) to -the captureSize. But the captureSize itself can be not in the -outputSizes, and then the adjusted configuration won't be -valid resulting in camera configuration failure. - -Signed-off-by: Andrey Konovalov -Signed-off-by: Hans de Goede -Tested-by: Bryan O'Donoghue # sc8280xp Lenovo x13s -Tested-by: Pavel Machek ---- - src/libcamera/pipeline/simple/simple.cpp | 7 +++++-- - 1 file changed, 5 insertions(+), 2 deletions(-) - -diff --git a/src/libcamera/pipeline/simple/simple.cpp b/src/libcamera/pipeline/simple/simple.cpp -index 911051b2..4d0e7255 100644 ---- a/src/libcamera/pipeline/simple/simple.cpp -+++ b/src/libcamera/pipeline/simple/simple.cpp -@@ -997,10 +997,13 @@ CameraConfiguration::Status SimpleCameraConfiguration::validate() - } - - if (!pipeConfig_->outputSizes.contains(cfg.size)) { -+ Size adjustedSize = pipeConfig_->captureSize; -+ if (!pipeConfig_->outputSizes.contains(adjustedSize)) -+ adjustedSize = pipeConfig_->outputSizes.max; - LOG(SimplePipeline, Debug) - << "Adjusting size from " << cfg.size -- << " to " << pipeConfig_->captureSize; -- cfg.size = pipeConfig_->captureSize; -+ << " to " << adjustedSize; -+ cfg.size = adjustedSize; - status = Adjusted; - } - --- -2.43.0 - diff --git a/modules/nixos/ipu6-softisp/libcamera/0002-libcamera-internal-Move-dma_heaps.-h-cpp-to-common-d.patch b/modules/nixos/ipu6-softisp/libcamera/0002-libcamera-internal-Move-dma_heaps.-h-cpp-to-common-d.patch deleted file mode 100644 index 85a27ba..0000000 --- a/modules/nixos/ipu6-softisp/libcamera/0002-libcamera-internal-Move-dma_heaps.-h-cpp-to-common-d.patch +++ /dev/null @@ -1,194 +0,0 @@ -From ca3bcfde49f069a85f7860f61d8c3bd196f97139 Mon Sep 17 00:00:00 2001 -From: Andrey Konovalov -Date: Tue, 26 Dec 2023 16:55:08 +0300 -Subject: [PATCH 02/25] libcamera: internal: Move dma_heaps.[h,cpp] to common - directories - -DmaHeap class is useful outside the RPi pipeline handler too. - -Move dma_heaps.h and dma_heaps.cpp to common directories. Update -the build files and RPi vc4 pipeline handler accordingly. - -Signed-off-by: Andrey Konovalov -Signed-off-by: Hans de Goede -Tested-by: Bryan O'Donoghue # sc8280xp Lenovo x13s -Tested-by: Pavel Machek ---- - .../libcamera/internal}/dma_heaps.h | 4 ---- - include/libcamera/internal/meson.build | 1 + - .../{pipeline/rpi/vc4 => }/dma_heaps.cpp | 18 +++++++----------- - src/libcamera/meson.build | 1 + - src/libcamera/pipeline/rpi/vc4/meson.build | 1 - - src/libcamera/pipeline/rpi/vc4/vc4.cpp | 5 ++--- - 6 files changed, 11 insertions(+), 19 deletions(-) - rename {src/libcamera/pipeline/rpi/vc4 => include/libcamera/internal}/dma_heaps.h (92%) - rename src/libcamera/{pipeline/rpi/vc4 => }/dma_heaps.cpp (83%) - -diff --git a/src/libcamera/pipeline/rpi/vc4/dma_heaps.h b/include/libcamera/internal/dma_heaps.h -similarity index 92% -rename from src/libcamera/pipeline/rpi/vc4/dma_heaps.h -rename to include/libcamera/internal/dma_heaps.h -index 0a4a8d86..cff8f140 100644 ---- a/src/libcamera/pipeline/rpi/vc4/dma_heaps.h -+++ b/include/libcamera/internal/dma_heaps.h -@@ -13,8 +13,6 @@ - - namespace libcamera { - --namespace RPi { -- - class DmaHeap - { - public: -@@ -27,6 +25,4 @@ private: - UniqueFD dmaHeapHandle_; - }; - --} /* namespace RPi */ -- - } /* namespace libcamera */ -diff --git a/include/libcamera/internal/meson.build b/include/libcamera/internal/meson.build -index 7f1f3440..33eb0fb3 100644 ---- a/include/libcamera/internal/meson.build -+++ b/include/libcamera/internal/meson.build -@@ -25,6 +25,7 @@ libcamera_internal_headers = files([ - 'device_enumerator.h', - 'device_enumerator_sysfs.h', - 'device_enumerator_udev.h', -+ 'dma_heaps.h', - 'formats.h', - 'framebuffer.h', - 'ipa_manager.h', -diff --git a/src/libcamera/pipeline/rpi/vc4/dma_heaps.cpp b/src/libcamera/dma_heaps.cpp -similarity index 83% -rename from src/libcamera/pipeline/rpi/vc4/dma_heaps.cpp -rename to src/libcamera/dma_heaps.cpp -index 317b1fc1..7444d9c2 100644 ---- a/src/libcamera/pipeline/rpi/vc4/dma_heaps.cpp -+++ b/src/libcamera/dma_heaps.cpp -@@ -5,8 +5,6 @@ - * dma_heaps.h - Helper class for dma-heap allocations. - */ - --#include "dma_heaps.h" -- - #include - #include - #include -@@ -16,6 +14,8 @@ - - #include - -+#include "libcamera/internal/dma_heaps.h" -+ - /* - * /dev/dma-heap/linux,cma is the dma-heap allocator, which allows dmaheap-cma - * to only have to worry about importing. -@@ -30,9 +30,7 @@ static constexpr std::array heapNames = { - - namespace libcamera { - --LOG_DECLARE_CATEGORY(RPI) -- --namespace RPi { -+LOG_DEFINE_CATEGORY(DmaHeap) - - DmaHeap::DmaHeap() - { -@@ -40,7 +38,7 @@ DmaHeap::DmaHeap() - int ret = ::open(name, O_RDWR | O_CLOEXEC, 0); - if (ret < 0) { - ret = errno; -- LOG(RPI, Debug) << "Failed to open " << name << ": " -+ LOG(DmaHeap, Debug) << "Failed to open " << name << ": " - << strerror(ret); - continue; - } -@@ -50,7 +48,7 @@ DmaHeap::DmaHeap() - } - - if (!dmaHeapHandle_.isValid()) -- LOG(RPI, Error) << "Could not open any dmaHeap device"; -+ LOG(DmaHeap, Error) << "Could not open any dmaHeap device"; - } - - DmaHeap::~DmaHeap() = default; -@@ -69,7 +67,7 @@ UniqueFD DmaHeap::alloc(const char *name, std::size_t size) - - ret = ::ioctl(dmaHeapHandle_.get(), DMA_HEAP_IOCTL_ALLOC, &alloc); - if (ret < 0) { -- LOG(RPI, Error) << "dmaHeap allocation failure for " -+ LOG(DmaHeap, Error) << "dmaHeap allocation failure for " - << name; - return {}; - } -@@ -77,7 +75,7 @@ UniqueFD DmaHeap::alloc(const char *name, std::size_t size) - UniqueFD allocFd(alloc.fd); - ret = ::ioctl(allocFd.get(), DMA_BUF_SET_NAME, name); - if (ret < 0) { -- LOG(RPI, Error) << "dmaHeap naming failure for " -+ LOG(DmaHeap, Error) << "dmaHeap naming failure for " - << name; - return {}; - } -@@ -85,6 +83,4 @@ UniqueFD DmaHeap::alloc(const char *name, std::size_t size) - return allocFd; - } - --} /* namespace RPi */ -- - } /* namespace libcamera */ -diff --git a/src/libcamera/meson.build b/src/libcamera/meson.build -index 45f63e93..3c5e43df 100644 ---- a/src/libcamera/meson.build -+++ b/src/libcamera/meson.build -@@ -17,6 +17,7 @@ libcamera_sources = files([ - 'delayed_controls.cpp', - 'device_enumerator.cpp', - 'device_enumerator_sysfs.cpp', -+ 'dma_heaps.cpp', - 'fence.cpp', - 'formats.cpp', - 'framebuffer.cpp', -diff --git a/src/libcamera/pipeline/rpi/vc4/meson.build b/src/libcamera/pipeline/rpi/vc4/meson.build -index cdb049c5..386e2296 100644 ---- a/src/libcamera/pipeline/rpi/vc4/meson.build -+++ b/src/libcamera/pipeline/rpi/vc4/meson.build -@@ -1,7 +1,6 @@ - # SPDX-License-Identifier: CC0-1.0 - - libcamera_sources += files([ -- 'dma_heaps.cpp', - 'vc4.cpp', - ]) - -diff --git a/src/libcamera/pipeline/rpi/vc4/vc4.cpp b/src/libcamera/pipeline/rpi/vc4/vc4.cpp -index 26102ea7..3a42e75e 100644 ---- a/src/libcamera/pipeline/rpi/vc4/vc4.cpp -+++ b/src/libcamera/pipeline/rpi/vc4/vc4.cpp -@@ -12,12 +12,11 @@ - #include - - #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_; - - /* DMAHEAP allocation helper. */ -- RPi::DmaHeap dmaHeap_; -+ DmaHeap dmaHeap_; - SharedFD lsTable_; - - struct Config { --- -2.43.0 - diff --git a/modules/nixos/ipu6-softisp/libcamera/0003-libcamera-dma_heaps-extend-DmaHeap-class-to-support-.patch b/modules/nixos/ipu6-softisp/libcamera/0003-libcamera-dma_heaps-extend-DmaHeap-class-to-support-.patch deleted file mode 100644 index de81924..0000000 --- a/modules/nixos/ipu6-softisp/libcamera/0003-libcamera-dma_heaps-extend-DmaHeap-class-to-support-.patch +++ /dev/null @@ -1,121 +0,0 @@ -From 6d5f3b0b54df4ff66079675a4c1f0f0b76778e22 Mon Sep 17 00:00:00 2001 -From: Andrey Konovalov -Date: Wed, 10 Jan 2024 23:51:25 +0300 -Subject: [PATCH 03/25] libcamera: dma_heaps: extend DmaHeap class to support - system heap - -Add an argument to the constructor to specify dma heaps type(s) -to use. Can be DmaHeapFlag::Cma and/or DmaHeapFlag::System. -By default DmaHeapFlag::Cma is used. If both DmaHeapFlag::Cma and -DmaHeapFlag::System are set, CMA heap is tried first. - -Signed-off-by: Andrey Konovalov -Signed-off-by: Hans de Goede -Tested-by: Bryan O'Donoghue # sc8280xp Lenovo x13s -Tested-by: Pavel Machek ---- - include/libcamera/internal/dma_heaps.h | 12 +++++++- - src/libcamera/dma_heaps.cpp | 39 +++++++++++++++----------- - 2 files changed, 34 insertions(+), 17 deletions(-) - -diff --git a/include/libcamera/internal/dma_heaps.h b/include/libcamera/internal/dma_heaps.h -index cff8f140..22aa1007 100644 ---- a/include/libcamera/internal/dma_heaps.h -+++ b/include/libcamera/internal/dma_heaps.h -@@ -9,6 +9,7 @@ - - #include - -+#include - #include - - namespace libcamera { -@@ -16,7 +17,14 @@ namespace libcamera { - class DmaHeap - { - public: -- DmaHeap(); -+ enum class DmaHeapFlag { -+ Cma = (1 << 0), -+ System = (1 << 1), -+ }; -+ -+ using DmaHeapFlags = Flags; -+ -+ DmaHeap(DmaHeapFlags flags = DmaHeapFlag::Cma); - ~DmaHeap(); - bool isValid() const { return dmaHeapHandle_.isValid(); } - UniqueFD alloc(const char *name, std::size_t size); -@@ -25,4 +33,6 @@ private: - UniqueFD dmaHeapHandle_; - }; - -+LIBCAMERA_FLAGS_ENABLE_OPERATORS(DmaHeap::DmaHeapFlag) -+ - } /* namespace libcamera */ -diff --git a/src/libcamera/dma_heaps.cpp b/src/libcamera/dma_heaps.cpp -index 7444d9c2..177de31b 100644 ---- a/src/libcamera/dma_heaps.cpp -+++ b/src/libcamera/dma_heaps.cpp -@@ -16,6 +16,8 @@ - - #include "libcamera/internal/dma_heaps.h" - -+namespace libcamera { -+ - /* - * /dev/dma-heap/linux,cma is the dma-heap allocator, which allows dmaheap-cma - * to only have to worry about importing. -@@ -23,28 +25,33 @@ - * Annoyingly, should the cma heap size be specified on the kernel command line - * instead of DT, the heap gets named "reserved" instead. - */ --static constexpr std::array heapNames = { -- "/dev/dma_heap/linux,cma", -- "/dev/dma_heap/reserved" -+static constexpr std::array, 3> heapNames = { -+ /* CMA heap names first */ -+ std::make_pair(DmaHeap::DmaHeapFlag::Cma, "/dev/dma_heap/linux,cma"), -+ std::make_pair(DmaHeap::DmaHeapFlag::Cma, "/dev/dma_heap/reserved"), -+ std::make_pair(DmaHeap::DmaHeapFlag::System, "/dev/dma_heap/system") - }; - --namespace libcamera { -- - LOG_DEFINE_CATEGORY(DmaHeap) - --DmaHeap::DmaHeap() -+DmaHeap::DmaHeap(DmaHeapFlags flags) - { -- for (const char *name : heapNames) { -- int ret = ::open(name, O_RDWR | O_CLOEXEC, 0); -- if (ret < 0) { -- ret = errno; -- LOG(DmaHeap, Debug) << "Failed to open " << name << ": " -- << strerror(ret); -- continue; -- } -+ int ret; - -- dmaHeapHandle_ = UniqueFD(ret); -- break; -+ for (const auto &name : heapNames) { -+ if (flags & name.first) { -+ ret = ::open(name.second, O_RDWR | O_CLOEXEC, 0); -+ if (ret < 0) { -+ ret = errno; -+ LOG(DmaHeap, Debug) << "Failed to open " << name.second << ": " -+ << strerror(ret); -+ continue; -+ } -+ -+ LOG(DmaHeap, Debug) << "Using " << name.second; -+ dmaHeapHandle_ = UniqueFD(ret); -+ break; -+ } - } - - if (!dmaHeapHandle_.isValid()) --- -2.43.0 - diff --git a/modules/nixos/ipu6-softisp/libcamera/0004-libcamera-internal-Move-SharedMemObject-class-to-a-c.patch b/modules/nixos/ipu6-softisp/libcamera/0004-libcamera-internal-Move-SharedMemObject-class-to-a-c.patch deleted file mode 100644 index 0222707..0000000 --- a/modules/nixos/ipu6-softisp/libcamera/0004-libcamera-internal-Move-SharedMemObject-class-to-a-c.patch +++ /dev/null @@ -1,57 +0,0 @@ -From 006a4a31a6803e92ec67f48b66da2cdff8b2f6ab Mon Sep 17 00:00:00 2001 -From: Andrey Konovalov -Date: Sun, 29 Oct 2023 15:56:48 +0300 -Subject: [PATCH 04/25] libcamera: internal: Move SharedMemObject class to a - common directory - -Move SharedMemObject class out of RPi namespace and put it into -include/libcamera/internal so that everyone could use it. - -Signed-off-by: Andrey Konovalov -Signed-off-by: Hans de Goede -Tested-by: Bryan O'Donoghue # sc8280xp Lenovo x13s -Tested-by: Pavel Machek ---- - include/libcamera/internal/meson.build | 1 + - .../common => include/libcamera/internal}/shared_mem_object.h | 4 ---- - 2 files changed, 1 insertion(+), 4 deletions(-) - rename {src/libcamera/pipeline/rpi/common => include/libcamera/internal}/shared_mem_object.h (98%) - -diff --git a/include/libcamera/internal/meson.build b/include/libcamera/internal/meson.build -index 33eb0fb3..5807dfd9 100644 ---- a/include/libcamera/internal/meson.build -+++ b/include/libcamera/internal/meson.build -@@ -39,6 +39,7 @@ libcamera_internal_headers = files([ - 'process.h', - 'pub_key.h', - 'request.h', -+ 'shared_mem_object.h', - 'source_paths.h', - 'sysfs.h', - 'v4l2_device.h', -diff --git a/src/libcamera/pipeline/rpi/common/shared_mem_object.h b/include/libcamera/internal/shared_mem_object.h -similarity index 98% -rename from src/libcamera/pipeline/rpi/common/shared_mem_object.h -rename to include/libcamera/internal/shared_mem_object.h -index aa56c220..bfb639ee 100644 ---- a/src/libcamera/pipeline/rpi/common/shared_mem_object.h -+++ b/include/libcamera/internal/shared_mem_object.h -@@ -19,8 +19,6 @@ - - namespace libcamera { - --namespace RPi { -- - template - class SharedMemObject - { -@@ -123,6 +121,4 @@ private: - T *obj_; - }; - --} /* namespace RPi */ -- - } /* namespace libcamera */ --- -2.43.0 - diff --git a/modules/nixos/ipu6-softisp/libcamera/0005-libcamera-internal-Document-the-SharedMemObject-clas.patch b/modules/nixos/ipu6-softisp/libcamera/0005-libcamera-internal-Document-the-SharedMemObject-clas.patch deleted file mode 100644 index a20d270..0000000 --- a/modules/nixos/ipu6-softisp/libcamera/0005-libcamera-internal-Document-the-SharedMemObject-clas.patch +++ /dev/null @@ -1,139 +0,0 @@ -From cb9ff82efd82af8ae26b2aca4183928c74f7ef34 Mon Sep 17 00:00:00 2001 -From: Dennis Bonke -Date: Wed, 20 Dec 2023 16:22:29 +0100 -Subject: [PATCH 05/25] libcamera: internal: Document the SharedMemObject class - -Document the SharedMemObject class. - -Signed-off-by: Dennis Bonke -Signed-off-by: Hans de Goede -Tested-by: Bryan O'Donoghue # sc8280xp Lenovo x13s -Tested-by: Pavel Machek ---- - .../libcamera/internal/shared_mem_object.h | 53 +++++++++++++++++++ - 1 file changed, 53 insertions(+) - -diff --git a/include/libcamera/internal/shared_mem_object.h b/include/libcamera/internal/shared_mem_object.h -index bfb639ee..e862ce48 100644 ---- a/include/libcamera/internal/shared_mem_object.h -+++ b/include/libcamera/internal/shared_mem_object.h -@@ -19,10 +19,20 @@ - - namespace libcamera { - -+/** -+ * \class SharedMemObject -+ * \brief Helper class for shared memory allocations. -+ * -+ * Takes a template T which is used to indicate the -+ * data type of the object stored. -+ */ - template - class SharedMemObject - { - public: -+ /** -+ * \brief The size of the object that is going to be stored here. -+ */ - static constexpr std::size_t SIZE = sizeof(T); - - SharedMemObject() -@@ -30,6 +40,11 @@ public: - { - } - -+ /** -+ * \brief Contstructor for the SharedMemObject. -+ * \param[in] name The requested name. -+ * \param[in] args Any additional args. -+ */ - template - SharedMemObject(const std::string &name, Args &&...args) - : name_(name), obj_(nullptr) -@@ -57,6 +72,10 @@ public: - obj_ = new (mem) T(std::forward(args)...); - } - -+ /** -+ * \brief Move constructor for SharedMemObject. -+ * \param[in] rhs The object to move. -+ */ - SharedMemObject(SharedMemObject &&rhs) - { - this->name_ = std::move(rhs.name_); -@@ -76,6 +95,10 @@ public: - /* Make SharedMemObject non-copyable for now. */ - LIBCAMERA_DISABLE_COPY(SharedMemObject) - -+ /** -+ * \brief Operator= for SharedMemObject. -+ * \param[in] rhs The SharedMemObject object to take the data from. -+ */ - SharedMemObject &operator=(SharedMemObject &&rhs) - { - this->name_ = std::move(rhs.name_); -@@ -85,31 +108,61 @@ public: - return *this; - } - -+ /** -+ * \brief Operator-> for SharedMemObject. -+ * -+ * \return the object. -+ */ - T *operator->() - { - return obj_; - } - -+ /** -+ * \brief Operator-> for SharedMemObject. -+ * -+ * \return the object. -+ */ - const T *operator->() const - { - return obj_; - } - -+ /** -+ * \brief Operator* for SharedMemObject. -+ * -+ * \return the object. -+ */ - T &operator*() - { - return *obj_; - } - -+ /** -+ * \brief Operator* for SharedMemObject. -+ * -+ * \return the object. -+ */ - const T &operator*() const - { - return *obj_; - } - -+ /** -+ * \brief Gets the file descriptor for the underlaying storage file. -+ * -+ * \return the file descriptor. -+ */ - const SharedFD &fd() const - { - return fd_; - } - -+ /** -+ * \brief Operator bool() for SharedMemObject. -+ * -+ * \return true if the object is not null, false otherwise. -+ */ - explicit operator bool() const - { - return !!obj_; --- -2.43.0 - diff --git a/modules/nixos/ipu6-softisp/libcamera/0006-libcamera-introduce-SoftwareIsp-class.patch b/modules/nixos/ipu6-softisp/libcamera/0006-libcamera-introduce-SoftwareIsp-class.patch deleted file mode 100644 index ebda98d..0000000 --- a/modules/nixos/ipu6-softisp/libcamera/0006-libcamera-introduce-SoftwareIsp-class.patch +++ /dev/null @@ -1,354 +0,0 @@ -From 3fa62a8e2f34c9794ba67e2565db8fef22938fa4 Mon Sep 17 00:00:00 2001 -From: Andrey Konovalov -Date: Sun, 22 Oct 2023 17:49:32 +0300 -Subject: [PATCH 06/25] libcamera: introduce SoftwareIsp class - -Doxygen documentation by Dennis Bonke. - -Co-authored-by: Dennis Bonke -Signed-off-by: Dennis Bonke -Signed-off-by: Andrey Konovalov -Signed-off-by: Hans de Goede -Tested-by: Bryan O'Donoghue # sc8280xp Lenovo x13s -Tested-by: Pavel Machek ---- - include/libcamera/internal/meson.build | 1 + - include/libcamera/internal/software_isp.h | 231 ++++++++++++++++++++++ - src/libcamera/meson.build | 1 + - src/libcamera/software_isp.cpp | 62 ++++++ - 4 files changed, 295 insertions(+) - create mode 100644 include/libcamera/internal/software_isp.h - create mode 100644 src/libcamera/software_isp.cpp - -diff --git a/include/libcamera/internal/meson.build b/include/libcamera/internal/meson.build -index 5807dfd9..1325941d 100644 ---- a/include/libcamera/internal/meson.build -+++ b/include/libcamera/internal/meson.build -@@ -40,6 +40,7 @@ libcamera_internal_headers = files([ - 'pub_key.h', - 'request.h', - 'shared_mem_object.h', -+ 'software_isp.h', - 'source_paths.h', - 'sysfs.h', - 'v4l2_device.h', -diff --git a/include/libcamera/internal/software_isp.h b/include/libcamera/internal/software_isp.h -new file mode 100644 -index 00000000..42ff48ec ---- /dev/null -+++ b/include/libcamera/internal/software_isp.h -@@ -0,0 +1,231 @@ -+/* SPDX-License-Identifier: LGPL-2.1-or-later */ -+/* -+ * Copyright (C) 2023, Linaro Ltd -+ * -+ * software_isp.h - Interface for a software implementation of an ISP -+ */ -+ -+#pragma once -+ -+#include -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include -+#include -+#include -+ -+#include -+ -+#include "libcamera/internal/pipeline_handler.h" -+ -+namespace libcamera { -+ -+class FrameBuffer; -+class PixelFormat; -+struct StreamConfiguration; -+ -+LOG_DECLARE_CATEGORY(SoftwareIsp) -+ -+/** -+ * \brief Base class for the Software ISP. -+ * -+ * Base class of the SoftwareIsp interface. -+ */ -+class SoftwareIsp -+{ -+public: -+ /** -+ * \brief Constructor for the SoftwareIsp object. -+ * \param[in] pipe The pipeline handler in use. -+ * \param[in] sensorControls The sensor controls. -+ */ -+ SoftwareIsp(PipelineHandler *pipe, const ControlInfoMap &sensorControls); -+ virtual ~SoftwareIsp(); -+ -+ /** -+ * \brief Load a configuration from a file. -+ * \param[in] filename The file to load from. -+ * -+ * \return 0 on success. -+ */ -+ virtual int loadConfiguration(const std::string &filename) = 0; -+ -+ /** -+ * \brief Gets if there is a valid debayer object. -+ * -+ * \returns true if there is, false otherwise. -+ */ -+ virtual bool isValid() const = 0; -+ -+ /** -+ * \brief Get the supported output formats. -+ * \param[in] input The input format. -+ * -+ * \return all supported output formats or an empty vector if there are none. -+ */ -+ virtual std::vector formats(PixelFormat input) = 0; -+ -+ /** -+ * \brief Get the supported output sizes for the given input format and size. -+ * \param[in] inputFormat The input format. -+ * \param[in] inputSize The input size. -+ * -+ * \return The valid size ranges or an empty range if there are none. -+ */ -+ virtual SizeRange sizes(PixelFormat inputFormat, const Size &inputSize) = 0; -+ -+ /** -+ * \brief Get the stride and the frame size. -+ * \param[in] pixelFormat The output format. -+ * \param[in] size The output size. -+ * -+ * \return a tuple of the stride and the frame size, or a tuple with 0,0 if there is no valid output config. -+ */ -+ virtual std::tuple -+ strideAndFrameSize(const PixelFormat &pixelFormat, const Size &size) = 0; -+ -+ /** -+ * \brief Configure the SwIspSimple object according to the passed in parameters. -+ * \param[in] inputCfg The input configuration. -+ * \param[in] outputCfgs The output configurations. -+ * \param[in] sensorControls The sensor controls. -+ * -+ * \return 0 on success, a negative errno on failure. -+ */ -+ virtual int configure(const StreamConfiguration &inputCfg, -+ const std::vector> &outputCfgs, -+ const ControlInfoMap &sensorControls) = 0; -+ -+ /** -+ * \brief Exports the buffers for use in processing. -+ * \param[in] output The number of outputs requested. -+ * \param[in] count The number of planes. -+ * \param[out] buffers The exported buffers. -+ * -+ * \return count when successful, a negative return value if an error occurred. -+ */ -+ virtual int exportBuffers(unsigned int output, unsigned int count, -+ std::vector> *buffers) = 0; -+ -+ /** -+ * \brief Starts the Software ISP worker. -+ * -+ * \return 0 on success, any other value indicates an error. -+ */ -+ virtual int start() = 0; -+ -+ /** -+ * \brief Stops the Software ISP worker. -+ */ -+ virtual void stop() = 0; -+ -+ /** -+ * \brief Queues buffers for processing. -+ * \param[in] input The input framebuffer. -+ * \param[in] outputs The output framebuffers. -+ * -+ * \return 0 on success, a negative errno on failure -+ */ -+ virtual int queueBuffers(FrameBuffer *input, -+ const std::map &outputs) = 0; -+ -+ /** -+ * \brief Process the statistics gathered. -+ * \param[in] sensorControls The sensor controls. -+ */ -+ virtual void processStats(const ControlList &sensorControls) = 0; // rather merge with queueBuffers()? -+ -+ /** -+ * \brief Get the signal for when the sensor controls are set. -+ * -+ * \return The control list of the sensor controls. -+ */ -+ virtual Signal &getSignalSetSensorControls() = 0; -+ -+ /** -+ * \brief Signals that the input buffer is ready. -+ */ -+ Signal inputBufferReady; -+ /** -+ * \brief Signals that the output buffer is ready. -+ */ -+ Signal outputBufferReady; -+ -+ /** -+ * \brief Signals that the ISP stats are ready. -+ * -+ * The int parameter isn't actually used. -+ */ -+ Signal ispStatsReady; -+}; -+ -+/** -+ * \brief Base class for the Software ISP Factory. -+ * -+ * Base class of the SoftwareIsp Factory. -+ */ -+class SoftwareIspFactoryBase -+{ -+public: -+ SoftwareIspFactoryBase(); -+ virtual ~SoftwareIspFactoryBase() = default; -+ -+ /** -+ * \brief Creates a SoftwareIsp object. -+ * \param[in] pipe The pipeline handler in use. -+ * \param[in] sensorControls The sensor controls. -+ * -+ * \return An unique pointer to the created SoftwareIsp object. -+ */ -+ static std::unique_ptr create(PipelineHandler *pipe, -+ const ControlInfoMap &sensorControls); -+ /** -+ * \brief Gives back a pointer to the factory. -+ * -+ * \return A static pointer to the factory instance. -+ */ -+ static SoftwareIspFactoryBase *&factory(); -+ -+private: -+ LIBCAMERA_DISABLE_COPY_AND_MOVE(SoftwareIspFactoryBase) -+ -+ static void registerType(SoftwareIspFactoryBase *factory); -+ virtual std::unique_ptr createInstance(PipelineHandler *pipe, -+ const ControlInfoMap &sensorControls) const = 0; -+}; -+ -+/** -+ * \brief Implementation for the Software ISP Factory. -+ */ -+template -+class SoftwareIspFactory : public SoftwareIspFactoryBase -+{ -+public: -+ SoftwareIspFactory() -+ : SoftwareIspFactoryBase() -+ { -+ } -+ -+ /** -+ * \brief Creates an instance of a SoftwareIsp object. -+ * \param[in] pipe The pipeline handler in use. -+ * \param[in] sensorControls The sensor controls. -+ * -+ * \return An unique pointer to the created SoftwareIsp object. -+ */ -+ std::unique_ptr createInstance(PipelineHandler *pipe, -+ const ControlInfoMap &sensorControls) const override -+ { -+ return std::make_unique<_SoftwareIsp>(pipe, sensorControls); -+ } -+}; -+ -+#define REGISTER_SOFTWAREISP(softwareIsp) \ -+ static SoftwareIspFactory global_##softwareIsp##Factory; -+ -+} /* namespace libcamera */ -diff --git a/src/libcamera/meson.build b/src/libcamera/meson.build -index 3c5e43df..86494663 100644 ---- a/src/libcamera/meson.build -+++ b/src/libcamera/meson.build -@@ -41,6 +41,7 @@ libcamera_sources = files([ - 'process.cpp', - 'pub_key.cpp', - 'request.cpp', -+ 'software_isp.cpp', - 'source_paths.cpp', - 'stream.cpp', - 'sysfs.cpp', -diff --git a/src/libcamera/software_isp.cpp b/src/libcamera/software_isp.cpp -new file mode 100644 -index 00000000..2ff97d70 ---- /dev/null -+++ b/src/libcamera/software_isp.cpp -@@ -0,0 +1,62 @@ -+/* SPDX-License-Identifier: LGPL-2.1-or-later */ -+/* -+ * Copyright (C) 2023, Linaro Ltd -+ * -+ * software_isp.cpp - Interface for a software implementation of an ISP -+ */ -+ -+#include "libcamera/internal/software_isp.h" -+ -+#include -+ -+namespace libcamera { -+ -+LOG_DEFINE_CATEGORY(SoftwareIsp) -+ -+SoftwareIsp::SoftwareIsp([[maybe_unused]] PipelineHandler *pipe, -+ [[maybe_unused]] const ControlInfoMap &sensorControls) -+{ -+} -+ -+SoftwareIsp::~SoftwareIsp() -+{ -+} -+ -+/* SoftwareIspFactoryBase */ -+ -+SoftwareIspFactoryBase::SoftwareIspFactoryBase() -+{ -+ registerType(this); -+} -+ -+void SoftwareIspFactoryBase::registerType(SoftwareIspFactoryBase *factory) -+{ -+ SoftwareIspFactoryBase *®istered = -+ SoftwareIspFactoryBase::factory(); -+ -+ ASSERT(!registered && factory); -+ registered = factory; -+} -+ -+SoftwareIspFactoryBase *&SoftwareIspFactoryBase::factory() -+{ -+ static SoftwareIspFactoryBase *factory; -+ return factory; -+} -+ -+std::unique_ptr -+SoftwareIspFactoryBase::create(PipelineHandler *pipe, -+ const ControlInfoMap &sensorControls) -+{ -+ SoftwareIspFactoryBase *factory = SoftwareIspFactoryBase::factory(); -+ if (!factory) -+ return nullptr; -+ -+ std::unique_ptr swIsp = factory->createInstance(pipe, sensorControls); -+ if (swIsp->isValid()) -+ return swIsp; -+ -+ return nullptr; -+} -+ -+} /* namespace libcamera */ --- -2.43.0 - diff --git a/modules/nixos/ipu6-softisp/libcamera/0007-libcamera-software_isp-Add-SwStats-base-class.patch b/modules/nixos/ipu6-softisp/libcamera/0007-libcamera-software_isp-Add-SwStats-base-class.patch deleted file mode 100644 index 9d6f2ac..0000000 --- a/modules/nixos/ipu6-softisp/libcamera/0007-libcamera-software_isp-Add-SwStats-base-class.patch +++ /dev/null @@ -1,382 +0,0 @@ -From ca3bb6ddf5307537aa05e43d3ec1ff7ffdc0efed Mon Sep 17 00:00:00 2001 -From: Hans de Goede -Date: Thu, 7 Dec 2023 13:30:27 +0100 -Subject: [PATCH 07/25] libcamera: software_isp: Add SwStats base class - -Add a virtual base class for CPU based software statistics gathering -implementations. - -The idea is for the implementations to offer a configure function + -functions to gather statistics on a line by line basis. This allows -CPU based software debayering to call into interlace debayering and -statistics gathering on a line by line bases while the input data -is still hot in the cache. - -This base class also allows the user of an implementation to specify -a window over which to gather statistics instead of processing the -whole frame; and it allows the implementation to choose to only -process 1/2, 1/4th, etc. of the lines instead of processing all -lines (in the window) by setting y_skip_mask_ from configure(). -Skipping columns is left up the line-processing functions provided -by the implementation. - -Doxygen documentation by Dennis Bonke. - -Co-authored-by: Dennis Bonke -Signed-off-by: Dennis Bonke -Co-authored-by: Andrey Konovalov -Signed-off-by: Andrey Konovalov -Signed-off-by: Hans de Goede -Tested-by: Bryan O'Donoghue # sc8280xp Lenovo x13s -Tested-by: Pavel Machek ---- - include/libcamera/internal/meson.build | 1 + - .../internal/software_isp/meson.build | 6 + - .../internal/software_isp/swisp_stats.h | 34 +++ - .../libcamera/internal/software_isp/swstats.h | 215 ++++++++++++++++++ - src/libcamera/meson.build | 1 + - src/libcamera/software_isp/meson.build | 5 + - src/libcamera/software_isp/swstats.cpp | 22 ++ - 7 files changed, 284 insertions(+) - create mode 100644 include/libcamera/internal/software_isp/meson.build - create mode 100644 include/libcamera/internal/software_isp/swisp_stats.h - create mode 100644 include/libcamera/internal/software_isp/swstats.h - create mode 100644 src/libcamera/software_isp/meson.build - create mode 100644 src/libcamera/software_isp/swstats.cpp - -diff --git a/include/libcamera/internal/meson.build b/include/libcamera/internal/meson.build -index 1325941d..caa533c4 100644 ---- a/include/libcamera/internal/meson.build -+++ b/include/libcamera/internal/meson.build -@@ -51,3 +51,4 @@ libcamera_internal_headers = files([ - ]) - - subdir('converter') -+subdir('software_isp') -diff --git a/include/libcamera/internal/software_isp/meson.build b/include/libcamera/internal/software_isp/meson.build -new file mode 100644 -index 00000000..1c43acc4 ---- /dev/null -+++ b/include/libcamera/internal/software_isp/meson.build -@@ -0,0 +1,6 @@ -+# SPDX-License-Identifier: CC0-1.0 -+ -+libcamera_internal_headers += files([ -+ 'swisp_stats.h', -+ 'swstats.h', -+]) -diff --git a/include/libcamera/internal/software_isp/swisp_stats.h b/include/libcamera/internal/software_isp/swisp_stats.h -new file mode 100644 -index 00000000..07ba7d6a ---- /dev/null -+++ b/include/libcamera/internal/software_isp/swisp_stats.h -@@ -0,0 +1,34 @@ -+/* SPDX-License-Identifier: LGPL-2.1-or-later */ -+/* -+ * Copyright (C) 2023, Linaro Ltd -+ * -+ * swisp_stats.h - Statistics data format used by the software ISP and software IPA -+ */ -+ -+#pragma once -+ -+namespace libcamera { -+ -+/** -+ * \brief Struct that holds the statistics for the Software ISP. -+ */ -+struct SwIspStats { -+ /** -+ * \brief Holds the sum of all sampled red pixels. -+ */ -+ unsigned long sumR_; -+ /** -+ * \brief Holds the sum of all sampled green pixels. -+ */ -+ unsigned long sumG_; -+ /** -+ * \brief Holds the sum of all sampled blue pixels. -+ */ -+ unsigned long sumB_; -+ /** -+ * \brief A histogram of luminance values. -+ */ -+ unsigned int y_histogram[16]; -+}; -+ -+} /* namespace libcamera */ -diff --git a/include/libcamera/internal/software_isp/swstats.h b/include/libcamera/internal/software_isp/swstats.h -new file mode 100644 -index 00000000..dcac7064 ---- /dev/null -+++ b/include/libcamera/internal/software_isp/swstats.h -@@ -0,0 +1,215 @@ -+/* SPDX-License-Identifier: LGPL-2.1-or-later */ -+/* -+ * Copyright (C) 2023, Linaro Ltd -+ * Copyright (C) 2023, Red Hat Inc. -+ * -+ * Authors: -+ * Hans de Goede -+ * -+ * swstats.h - software statistics base class -+ */ -+ -+#pragma once -+ -+#include -+ -+#include -+#include -+ -+#include -+ -+namespace libcamera { -+ -+class PixelFormat; -+struct SharedFD; -+struct StreamConfiguration; -+ -+LOG_DECLARE_CATEGORY(SwStats) -+ -+/** -+ * \class SwStats -+ * \brief Base class for the software ISP statistics. -+ * -+ * Base class for the software ISP statistics. -+ */ -+class SwStats -+{ -+public: -+ virtual ~SwStats() = 0; -+ -+ /** -+ * \brief Gets wether the statistics object is valid. -+ * -+ * \return true if it's valid, false otherwise. -+ */ -+ virtual bool isValid() const = 0; -+ -+ /** -+ * \brief Configure the statistics object for the passed in input format. -+ * \param[in] inputCfg The input format -+ * -+ * \return 0 on success, a negative errno value on failure. -+ */ -+ virtual int configure(const StreamConfiguration &inputCfg) = 0; -+ -+ /** -+ * \brief Get the file descriptor for the statistics. -+ * -+ * \return the file descriptor -+ */ -+ virtual const SharedFD &getStatsFD() = 0; -+ -+protected: -+ /** -+ * \brief Called when there is data to get statistics from. -+ * \param[in] src The input data -+ */ -+ typedef void (SwStats::*statsProcessFn)(const uint8_t *src[]); -+ /** -+ * \brief Called when the statistics gathering is done or when a new frame starts. -+ */ -+ typedef void (SwStats::*statsVoidFn)(); -+ -+ /* Variables set by configure(), used every line */ -+ /** -+ * \brief The function called when a line is ready for statistics processing. -+ * -+ * Used for line 0 and 1, repeating if there isn't a 3rd and a 4th line in the bayer order. -+ */ -+ statsProcessFn stats0_; -+ /** -+ * \brief The function called when a line is ready for statistics processing. -+ * -+ * Used for line 3 and 4, only needed if the bayer order has 4 different lines. -+ */ -+ statsProcessFn stats2_; -+ -+ /** -+ * \brief The memory used per pixel in bits. -+ */ -+ unsigned int bpp_; -+ /** -+ * \brief Skip lines where this bitmask is set in y. -+ */ -+ unsigned int y_skip_mask_; -+ -+ /** -+ * \brief Statistics window, set by setWindow(), used ever line. -+ */ -+ Rectangle window_; -+ -+ /** -+ * \brief The function called at the start of a frame. -+ */ -+ statsVoidFn startFrame_; -+ /** -+ * \brief The function called at the end of a frame. -+ */ -+ statsVoidFn finishFrame_; -+ /** -+ * \brief The size of the bayer pattern. -+ */ -+ Size patternSize_; -+ /** -+ * \brief The offset of x, applied to window_.x for bayer variants. -+ * -+ * This can either be 0 or 1. -+ */ -+ unsigned int x_shift_; -+ -+public: -+ /** -+ * \brief Get the pattern size. -+ * -+ * For some input-formats, e.g. Bayer data, processing is done multiple lines -+ * and/or columns at a time. Get width and height at which the (bayer) pattern -+ * repeats. Window values are rounded down to a multiple of this and the height -+ * also indicates if processLine2() should be called or not. -+ * This may only be called after a successful configure() call. -+ * -+ * \return the pattern size. -+ */ -+ const Size &patternSize() { return patternSize_; } -+ -+ /** -+ * \brief Specify window coordinates over which to gather statistics. -+ * \param[in] window The window object. -+ */ -+ void setWindow(Rectangle window) -+ { -+ window_ = window; -+ -+ window_.x &= ~(patternSize_.width - 1); -+ window_.x += x_shift_; -+ window_.y &= ~(patternSize_.height - 1); -+ -+ /* width_ - x_shift_ to make sure the window fits */ -+ window_.width -= x_shift_; -+ window_.width &= ~(patternSize_.width - 1); -+ window_.height &= ~(patternSize_.height - 1); -+ } -+ -+ /** -+ * \brief Reset state to start statistics gathering for a new frame. -+ * -+ * This may only be called after a successful setWindow() call. -+ */ -+ void startFrame() -+ { -+ (this->*startFrame_)(); -+ } -+ -+ /** -+ * \brief Process line 0. -+ * \param[in] y The y coordinate. -+ * \param[in] src The input data. -+ * -+ * This function processes line 0 for input formats with patternSize height == 1. -+ * It'll process line 0 and 1 for input formats with patternSize height >= 2. -+ * This function may only be called after a successful setWindow() call. -+ */ -+ void processLine0(unsigned int y, const uint8_t *src[]) -+ { -+ if ((y & y_skip_mask_) || y < (unsigned int)window_.y || -+ y >= (window_.y + window_.height)) -+ return; -+ -+ (this->*stats0_)(src); -+ } -+ -+ /** -+ * \brief Process line 2 and 3. -+ * \param[in] y The y coordinate. -+ * \param[in] src The input data. -+ * -+ * This function processes line 2 and 3 for input formats with patternSize height == 4. -+ * This function may only be called after a successful setWindow() call. -+ */ -+ void processLine2(unsigned int y, const uint8_t *src[]) -+ { -+ if ((y & y_skip_mask_) || y < (unsigned int)window_.y || -+ y >= (window_.y + window_.height)) -+ return; -+ -+ (this->*stats2_)(src); -+ } -+ -+ /** -+ * \brief Finish statistics calculation for the current frame. -+ * -+ * This may only be called after a successful setWindow() call. -+ */ -+ void finishFrame() -+ { -+ (this->*finishFrame_)(); -+ } -+ -+ /** -+ * \brief Signals that the statistics are ready. -+ * -+ * The int parameter isn't actually used. -+ */ -+ Signal statsReady; -+}; -+ -+} /* namespace libcamera */ -diff --git a/src/libcamera/meson.build b/src/libcamera/meson.build -index 86494663..3d63e8a2 100644 ---- a/src/libcamera/meson.build -+++ b/src/libcamera/meson.build -@@ -71,6 +71,7 @@ subdir('converter') - subdir('ipa') - subdir('pipeline') - subdir('proxy') -+subdir('software_isp') - - null_dep = dependency('', required : false) - -diff --git a/src/libcamera/software_isp/meson.build b/src/libcamera/software_isp/meson.build -new file mode 100644 -index 00000000..9359075d ---- /dev/null -+++ b/src/libcamera/software_isp/meson.build -@@ -0,0 +1,5 @@ -+# SPDX-License-Identifier: CC0-1.0 -+ -+libcamera_sources += files([ -+ 'swstats.cpp', -+]) -diff --git a/src/libcamera/software_isp/swstats.cpp b/src/libcamera/software_isp/swstats.cpp -new file mode 100644 -index 00000000..e65a7ada ---- /dev/null -+++ b/src/libcamera/software_isp/swstats.cpp -@@ -0,0 +1,22 @@ -+/* SPDX-License-Identifier: LGPL-2.1-or-later */ -+/* -+ * Copyright (C) 2023, Linaro Ltd -+ * Copyright (C) 2023, Red Hat Inc. -+ * -+ * Authors: -+ * Hans de Goede -+ * -+ * swstats.cpp - software statistics base class -+ */ -+ -+#include "libcamera/internal/software_isp/swstats.h" -+ -+namespace libcamera { -+ -+LOG_DEFINE_CATEGORY(SwStats) -+ -+SwStats::~SwStats() -+{ -+} -+ -+} /* namespace libcamera */ --- -2.43.0 - diff --git a/modules/nixos/ipu6-softisp/libcamera/0008-libcamera-software_isp-Add-SwStatsCpu-class.patch b/modules/nixos/ipu6-softisp/libcamera/0008-libcamera-software_isp-Add-SwStatsCpu-class.patch deleted file mode 100644 index d48eadd..0000000 --- a/modules/nixos/ipu6-softisp/libcamera/0008-libcamera-software_isp-Add-SwStatsCpu-class.patch +++ /dev/null @@ -1,272 +0,0 @@ -From c1c43445cd4408010e500fe9d6b6424c77bcf75d Mon Sep 17 00:00:00 2001 -From: Hans de Goede -Date: Fri, 8 Dec 2023 12:50:57 +0100 -Subject: [PATCH 08/25] libcamera: software_isp: Add SwStatsCpu class - -Add a CPU based SwStats implementation for SoftwareISP / SoftIPA use. - -Co-authored-by: Andrey Konovalov -Signed-off-by: Andrey Konovalov -Co-authored-by: Pavel Machek -Signed-off-by: Pavel Machek -Co-authored-by: Dennis Bonke -Signed-off-by: Dennis Bonke -Co-authored-by: Marttico -Signed-off-by: Marttico -Co-authored-by: Toon Langendam -Signed-off-by: Toon Langendam -Signed-off-by: Hans de Goede -Tested-by: Bryan O'Donoghue # sc8280xp Lenovo x13s -Tested-by: Pavel Machek ---- - .../internal/software_isp/meson.build | 1 + - .../internal/software_isp/swstats_cpu.h | 44 +++++ - src/libcamera/software_isp/meson.build | 1 + - src/libcamera/software_isp/swstats_cpu.cpp | 164 ++++++++++++++++++ - 4 files changed, 210 insertions(+) - create mode 100644 include/libcamera/internal/software_isp/swstats_cpu.h - create mode 100644 src/libcamera/software_isp/swstats_cpu.cpp - -diff --git a/include/libcamera/internal/software_isp/meson.build b/include/libcamera/internal/software_isp/meson.build -index 1c43acc4..1d9e4018 100644 ---- a/include/libcamera/internal/software_isp/meson.build -+++ b/include/libcamera/internal/software_isp/meson.build -@@ -3,4 +3,5 @@ - libcamera_internal_headers += files([ - 'swisp_stats.h', - 'swstats.h', -+ 'swstats_cpu.h', - ]) -diff --git a/include/libcamera/internal/software_isp/swstats_cpu.h b/include/libcamera/internal/software_isp/swstats_cpu.h -new file mode 100644 -index 00000000..8bb86e98 ---- /dev/null -+++ b/include/libcamera/internal/software_isp/swstats_cpu.h -@@ -0,0 +1,44 @@ -+/* SPDX-License-Identifier: LGPL-2.1-or-later */ -+/* -+ * Copyright (C) 2023, Linaro Ltd -+ * Copyright (C) 2023, Red Hat Inc. -+ * -+ * Authors: -+ * Hans de Goede -+ * -+ * swstats_cpu.h - CPU based software statistics implementation -+ */ -+ -+#pragma once -+ -+#include "libcamera/internal/shared_mem_object.h" -+#include "libcamera/internal/software_isp/swisp_stats.h" -+#include "libcamera/internal/software_isp/swstats.h" -+ -+namespace libcamera { -+ -+/** -+ * \class SwStatsCpu -+ * \brief Implementation for the Software statistics on the CPU. -+ */ -+class SwStatsCpu : public SwStats -+{ -+public: -+ SwStatsCpu(); -+ ~SwStatsCpu() { } -+ -+ bool isValid() const { return sharedStats_.fd().isValid(); } -+ const SharedFD &getStatsFD() { return sharedStats_.fd(); } -+ int configure(const StreamConfiguration &inputCfg); -+private: -+ void statsBGGR10PLine0(const uint8_t *src[]); -+ void statsGBRG10PLine0(const uint8_t *src[]); -+ void resetStats(void); -+ void finishStats(void); -+ -+ SharedMemObject sharedStats_; -+ SwIspStats stats_; -+ bool swap_lines_; -+}; -+ -+} /* namespace libcamera */ -diff --git a/src/libcamera/software_isp/meson.build b/src/libcamera/software_isp/meson.build -index 9359075d..d31c6217 100644 ---- a/src/libcamera/software_isp/meson.build -+++ b/src/libcamera/software_isp/meson.build -@@ -2,4 +2,5 @@ - - libcamera_sources += files([ - 'swstats.cpp', -+ 'swstats_cpu.cpp', - ]) -diff --git a/src/libcamera/software_isp/swstats_cpu.cpp b/src/libcamera/software_isp/swstats_cpu.cpp -new file mode 100644 -index 00000000..59453d07 ---- /dev/null -+++ b/src/libcamera/software_isp/swstats_cpu.cpp -@@ -0,0 +1,164 @@ -+/* SPDX-License-Identifier: LGPL-2.1-or-later */ -+/* -+ * Copyright (C) 2023, Linaro Ltd -+ * Copyright (C) 2023, Red Hat Inc. -+ * -+ * Authors: -+ * Hans de Goede -+ * -+ * swstats_cpu.cpp - CPU based software statistics implementation -+ */ -+ -+#include "libcamera/internal/software_isp/swstats_cpu.h" -+ -+#include -+ -+#include -+ -+#include "libcamera/internal/bayer_format.h" -+ -+namespace libcamera { -+ -+SwStatsCpu::SwStatsCpu() -+ : SwStats() -+{ -+ sharedStats_ = SharedMemObject("softIsp_stats"); -+ if (!sharedStats_.fd().isValid()) -+ LOG(SwStats, Error) -+ << "Failed to create shared memory for statistics"; -+} -+ -+/* for brightness values in the 0 to 255 range: */ -+static const unsigned int BRIGHT_LVL = 200U << 8; -+static const unsigned int TOO_BRIGHT_LVL = 240U << 8; -+ -+static const unsigned int RED_Y_MUL = 77; /* 0.30 * 256 */ -+static const unsigned int GREEN_Y_MUL = 150; /* 0.59 * 256 */ -+static const unsigned int BLUE_Y_MUL = 29; /* 0.11 * 256 */ -+ -+#define SWISP_LINARO_START_LINE_STATS(pixel_t) \ -+ pixel_t r, g, g2, b; \ -+ unsigned int y_val; \ -+ \ -+ unsigned int sumR = 0; \ -+ unsigned int sumG = 0; \ -+ unsigned int sumB = 0; -+ -+#define SWISP_LINARO_ACCUMULATE_LINE_STATS(div) \ -+ sumR += r; \ -+ sumG += g; \ -+ sumB += b; \ -+ \ -+ y_val = r * RED_Y_MUL; \ -+ y_val += g * GREEN_Y_MUL; \ -+ y_val += b * BLUE_Y_MUL; \ -+ stats_.y_histogram[y_val / (256 * 16 * (div))]++; -+ -+#define SWISP_LINARO_FINISH_LINE_STATS() \ -+ stats_.sumR_ += sumR; \ -+ stats_.sumG_ += sumG; \ -+ stats_.sumB_ += sumB; -+ -+static inline __attribute__((always_inline)) void -+statsBayer10P(const int width, const uint8_t *src0, const uint8_t *src1, bool bggr, SwIspStats &stats_) -+{ -+ const int width_in_bytes = width * 5 / 4; -+ -+ SWISP_LINARO_START_LINE_STATS(uint8_t) -+ -+ for (int x = 0; x < width_in_bytes; x += 5) { -+ if (bggr) { -+ /* BGGR */ -+ b = src0[x]; -+ g = src0[x + 1]; -+ g2 = src1[x]; -+ r = src1[x + 1]; -+ } else { -+ /* GBRG */ -+ g = src0[x]; -+ b = src0[x + 1]; -+ r = src1[x]; -+ g2 = src1[x + 1]; -+ } -+ g = (g + g2) / 2; -+ -+ SWISP_LINARO_ACCUMULATE_LINE_STATS(1) -+ } -+ -+ SWISP_LINARO_FINISH_LINE_STATS() -+} -+ -+void SwStatsCpu::statsBGGR10PLine0(const uint8_t *src[]) -+{ -+ const uint8_t *src0 = src[1] + window_.x * 5 / 4; -+ const uint8_t *src1 = src[2] + window_.x * 5 / 4; -+ -+ if (swap_lines_) -+ std::swap(src0, src1); -+ -+ statsBayer10P(window_.width, src0, src1, true, stats_); -+} -+ -+void SwStatsCpu::statsGBRG10PLine0(const uint8_t *src[]) -+{ -+ const uint8_t *src0 = src[1] + window_.x * 5 / 4; -+ const uint8_t *src1 = src[2] + window_.x * 5 / 4; -+ -+ if (swap_lines_) -+ std::swap(src0, src1); -+ -+ statsBayer10P(window_.width, src0, src1, false, stats_); -+} -+ -+void SwStatsCpu::resetStats(void) -+{ -+ stats_.sumR_ = 0; -+ stats_.sumB_ = 0; -+ stats_.sumG_ = 0; -+ std::fill_n(stats_.y_histogram, 16, 0); -+} -+ -+void SwStatsCpu::finishStats(void) -+{ -+ *sharedStats_ = stats_; -+ statsReady.emit(0); -+} -+ -+int SwStatsCpu::configure(const StreamConfiguration &inputCfg) -+{ -+ BayerFormat bayerFormat = -+ BayerFormat::fromPixelFormat(inputCfg.pixelFormat); -+ -+ startFrame_ = (SwStats::statsVoidFn)&SwStatsCpu::resetStats; -+ finishFrame_ = (SwStats::statsVoidFn)&SwStatsCpu::finishStats; -+ -+ if (bayerFormat.bitDepth == 10 && -+ bayerFormat.packing == BayerFormat::Packing::CSI2) { -+ bpp_ = 10; -+ patternSize_.height = 2; -+ patternSize_.width = 4; /* 5 bytes per *4* pixels */ -+ y_skip_mask_ = 0x02; /* Skip every 3th and 4th line */ -+ x_shift_ = 0; -+ -+ switch (bayerFormat.order) { -+ case BayerFormat::BGGR: -+ case BayerFormat::GRBG: -+ stats0_ = (SwStats::statsProcessFn)&SwStatsCpu::statsBGGR10PLine0; -+ swap_lines_ = bayerFormat.order == BayerFormat::GRBG; -+ return 0; -+ case BayerFormat::GBRG: -+ case BayerFormat::RGGB: -+ stats0_ = (SwStats::statsProcessFn)&SwStatsCpu::statsGBRG10PLine0; -+ swap_lines_ = bayerFormat.order == BayerFormat::RGGB; -+ return 0; -+ default: -+ break; -+ } -+ } -+ -+ LOG(SwStats, Info) -+ << "Unsupported input format " << inputCfg.pixelFormat.toString(); -+ return -EINVAL; -+} -+ -+} /* namespace libcamera */ --- -2.43.0 - diff --git a/modules/nixos/ipu6-softisp/libcamera/0009-libcamera-software_isp-Add-Debayer-base-class.patch b/modules/nixos/ipu6-softisp/libcamera/0009-libcamera-software_isp-Add-Debayer-base-class.patch deleted file mode 100644 index f43b336..0000000 --- a/modules/nixos/ipu6-softisp/libcamera/0009-libcamera-software_isp-Add-Debayer-base-class.patch +++ /dev/null @@ -1,272 +0,0 @@ -From 8fc77447c0d76b0b52b19d23674049181c6cf8d2 Mon Sep 17 00:00:00 2001 -From: Hans de Goede -Date: Mon, 11 Dec 2023 14:46:53 +0100 -Subject: [PATCH 09/25] libcamera: software_isp: Add Debayer base class - -Add a base class for debayer implementations. This is intended to be -suitable for both GPU (or otherwise) accelerated debayer implementations -as well as CPU based debayering. - -Doxygen documentation by Dennis Bonke. - -Co-authored-by: Dennis Bonke -Signed-off-by: Dennis Bonke -Co-authored-by: Andrey Konovalov -Signed-off-by: Andrey Konovalov -Signed-off-by: Hans de Goede -Tested-by: Bryan O'Donoghue # sc8280xp Lenovo x13s -Tested-by: Pavel Machek ---- - .../libcamera/internal/software_isp/debayer.h | 132 ++++++++++++++++++ - .../internal/software_isp/debayer_params.h | 43 ++++++ - .../internal/software_isp/meson.build | 2 + - src/libcamera/software_isp/debayer.cpp | 22 +++ - src/libcamera/software_isp/meson.build | 1 + - 5 files changed, 200 insertions(+) - create mode 100644 include/libcamera/internal/software_isp/debayer.h - create mode 100644 include/libcamera/internal/software_isp/debayer_params.h - create mode 100644 src/libcamera/software_isp/debayer.cpp - -diff --git a/include/libcamera/internal/software_isp/debayer.h b/include/libcamera/internal/software_isp/debayer.h -new file mode 100644 -index 00000000..39e6f393 ---- /dev/null -+++ b/include/libcamera/internal/software_isp/debayer.h -@@ -0,0 +1,132 @@ -+/* SPDX-License-Identifier: LGPL-2.1-or-later */ -+/* -+ * Copyright (C) 2023, Linaro Ltd -+ * Copyright (C) 2023, Red Hat Inc. -+ * -+ * Authors: -+ * Hans de Goede -+ * -+ * debayer.h - debayering base class -+ */ -+ -+#pragma once -+ -+#include -+ -+#include -+#include -+ -+#include -+#include -+ -+#include "libcamera/internal/software_isp/debayer_params.h" -+ -+namespace libcamera { -+ -+class FrameBuffer; -+ -+LOG_DECLARE_CATEGORY(Debayer) -+ -+/** -+ * \class Debayer -+ * \brief Base debayering class -+ * -+ * Base class that provides functions for setting up the debayering process. -+ */ -+class Debayer -+{ -+public: -+ virtual ~Debayer() = 0; -+ -+ /** -+ * \brief Configure the debayer object according to the passed in parameters. -+ * \param[in] inputCfg The input configuration. -+ * \param[in] outputCfgs The output configurations. -+ * -+ * \return 0 on success, a negative errno on failure. -+ */ -+ virtual int configure(const StreamConfiguration &inputCfg, -+ const std::vector> &outputCfgs) = 0; -+ -+ /** -+ * \brief Get the width and height at which the bayer pattern repeats. -+ * \param[in] inputFormat The input format. -+ * -+ * \return pattern size or an empty size for unsupported inputFormats. -+ */ -+ virtual Size patternSize(PixelFormat inputFormat) = 0; -+ -+ /** -+ * \brief Get the supported output formats. -+ * \param[in] inputFormat The input format. -+ * -+ * \return all supported output formats or an empty vector if there are none. -+ */ -+ virtual std::vector 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 -+ strideAndFrameSize(const PixelFormat &outputFormat, const Size &size) = 0; -+ -+ /** -+ * \brief Process the bayer data into the requested format. -+ * \param[in] input The input buffer. -+ * \param[in] output The output buffer. -+ * \param[in] params The parameters to be used in debayering. -+ * -+ * \note DebayerParams is passed by value deliberately so that a copy is passed -+ * when this is run in another thread by invokeMethod(). -+ */ -+ virtual void process(FrameBuffer *input, FrameBuffer *output, DebayerParams params) = 0; -+ -+ /** -+ * \brief Get the supported output sizes for the given input format and size. -+ * \param[in] inputFormat The input format. -+ * \param[in] inputSize The input size. -+ * -+ * \return The valid size ranges or an empty range if there are none. -+ */ -+ SizeRange sizes(PixelFormat inputFormat, const Size &inputSize) -+ { -+ Size pattern_size = patternSize(inputFormat); -+ -+ if (pattern_size.isNull()) -+ return {}; -+ -+ /* -+ * For debayer interpolation a border of pattern-height x pattern-width -+ * is kept around the entire image. Combined with a minimum-size of -+ * pattern-height x pattern-width this means the input-size needs to be -+ * at least (3 * pattern-height) x (3 * pattern-width). -+ */ -+ if (inputSize.width < (3 * pattern_size.width) || -+ inputSize.height < (3 * pattern_size.height)) { -+ LOG(Debayer, Warning) -+ << "Input format size too small: " << inputSize.toString(); -+ return {}; -+ } -+ -+ return SizeRange(Size(pattern_size.width, pattern_size.height), -+ Size((inputSize.width - 2 * pattern_size.width) & ~(pattern_size.width - 1), -+ (inputSize.height - 2 * pattern_size.height) & ~(pattern_size.height - 1)), -+ pattern_size.width, pattern_size.height); -+ } -+ -+ /** -+ * \brief Signals when the input buffer is ready. -+ */ -+ Signal inputBufferReady; -+ -+ /** -+ * \brief Signals when the output buffer is ready. -+ */ -+ Signal outputBufferReady; -+}; -+ -+} /* namespace libcamera */ -diff --git a/include/libcamera/internal/software_isp/debayer_params.h b/include/libcamera/internal/software_isp/debayer_params.h -new file mode 100644 -index 00000000..8f515304 ---- /dev/null -+++ b/include/libcamera/internal/software_isp/debayer_params.h -@@ -0,0 +1,43 @@ -+/* SPDX-License-Identifier: LGPL-2.1-or-later */ -+/* -+ * Copyright (C) 2023, Red Hat Inc. -+ * -+ * Authors: -+ * Hans de Goede -+ * -+ * swstats.h - software statistics base class -+ */ -+ -+#pragma once -+ -+namespace libcamera { -+ -+/** -+ * \brief Struct to hold the debayer parameters. -+ */ -+struct DebayerParams { -+ /** -+ * \brief Red Gain. -+ * -+ * 128 = 0.5, 256 = 1.0, 512 = 2.0, etc. -+ */ -+ unsigned int gainR; -+ /** -+ * \brief Green Gain. -+ * -+ * 128 = 0.5, 256 = 1.0, 512 = 2.0, etc. -+ */ -+ unsigned int gainG; -+ /** -+ * \brief Blue Gain. -+ * -+ * 128 = 0.5, 256 = 1.0, 512 = 2.0, etc. -+ */ -+ unsigned int gainB; -+ /** -+ * \brief Gamma correction, 1.0 is no correction. -+ */ -+ float gamma; -+}; -+ -+} /* namespace libcamera */ -diff --git a/include/libcamera/internal/software_isp/meson.build b/include/libcamera/internal/software_isp/meson.build -index 1d9e4018..7e40925e 100644 ---- a/include/libcamera/internal/software_isp/meson.build -+++ b/include/libcamera/internal/software_isp/meson.build -@@ -1,6 +1,8 @@ - # SPDX-License-Identifier: CC0-1.0 - - libcamera_internal_headers += files([ -+ 'debayer.h', -+ 'debayer_params.h', - 'swisp_stats.h', - 'swstats.h', - 'swstats_cpu.h', -diff --git a/src/libcamera/software_isp/debayer.cpp b/src/libcamera/software_isp/debayer.cpp -new file mode 100644 -index 00000000..442da1ac ---- /dev/null -+++ b/src/libcamera/software_isp/debayer.cpp -@@ -0,0 +1,22 @@ -+/* SPDX-License-Identifier: LGPL-2.1-or-later */ -+/* -+ * Copyright (C) 2023, Linaro Ltd -+ * Copyright (C) 2023, Red Hat Inc. -+ * -+ * Authors: -+ * Hans de Goede -+ * -+ * debayer.cpp - debayer base class -+ */ -+ -+#include "libcamera/internal/software_isp/debayer.h" -+ -+namespace libcamera { -+ -+LOG_DEFINE_CATEGORY(Debayer) -+ -+Debayer::~Debayer() -+{ -+} -+ -+} /* namespace libcamera */ -diff --git a/src/libcamera/software_isp/meson.build b/src/libcamera/software_isp/meson.build -index d31c6217..d4ae5ac7 100644 ---- a/src/libcamera/software_isp/meson.build -+++ b/src/libcamera/software_isp/meson.build -@@ -1,6 +1,7 @@ - # SPDX-License-Identifier: CC0-1.0 - - libcamera_sources += files([ -+ 'debayer.cpp', - 'swstats.cpp', - 'swstats_cpu.cpp', - ]) --- -2.43.0 - diff --git a/modules/nixos/ipu6-softisp/libcamera/0010-libcamera-software_isp-Add-DebayerCpu-class.patch b/modules/nixos/ipu6-softisp/libcamera/0010-libcamera-software_isp-Add-DebayerCpu-class.patch deleted file mode 100644 index 56bc873..0000000 --- a/modules/nixos/ipu6-softisp/libcamera/0010-libcamera-software_isp-Add-DebayerCpu-class.patch +++ /dev/null @@ -1,727 +0,0 @@ -From 7eb7164ed7d90ea4cf9ec7e4f35fa8efa25f35e9 Mon Sep 17 00:00:00 2001 -From: Hans de Goede -Date: Mon, 11 Dec 2023 17:00:17 +0100 -Subject: [PATCH 10/25] libcamera: software_isp: Add DebayerCpu class - -Add CPU based debayering implementation. This initial implementation -only supports debayering packed 10 bits per pixel bayer data in -the 4 standard bayer orders. - -Doxygen documentation by Dennis Bonke. - -Co-authored-by: Dennis Bonke -Signed-off-by: Dennis Bonke -Co-authored-by: Andrey Konovalov -Signed-off-by: Andrey Konovalov -Co-authored-by: Pavel Machek -Signed-off-by: Pavel Machek -Signed-off-by: Hans de Goede -Tested-by: Bryan O'Donoghue # sc8280xp Lenovo x13s -Tested-by: Pavel Machek ---- - .../internal/software_isp/debayer_cpu.h | 131 +++++ - .../internal/software_isp/meson.build | 1 + - src/libcamera/software_isp/debayer_cpu.cpp | 528 ++++++++++++++++++ - src/libcamera/software_isp/meson.build | 1 + - 4 files changed, 661 insertions(+) - create mode 100644 include/libcamera/internal/software_isp/debayer_cpu.h - create mode 100644 src/libcamera/software_isp/debayer_cpu.cpp - -diff --git a/include/libcamera/internal/software_isp/debayer_cpu.h b/include/libcamera/internal/software_isp/debayer_cpu.h -new file mode 100644 -index 00000000..78573f44 ---- /dev/null -+++ b/include/libcamera/internal/software_isp/debayer_cpu.h -@@ -0,0 +1,131 @@ -+/* SPDX-License-Identifier: LGPL-2.1-or-later */ -+/* -+ * Copyright (C) 2023, Linaro Ltd -+ * Copyright (C) 2023, Red Hat Inc. -+ * -+ * Authors: -+ * Hans de Goede -+ * -+ * debayer_cpu.h - CPU based debayering header -+ */ -+ -+#pragma once -+ -+#include -+#include -+#include -+ -+#include -+ -+#include "libcamera/internal/software_isp/swstats_cpu.h" -+#include "libcamera/internal/software_isp/debayer.h" -+ -+namespace libcamera { -+ -+/** -+ * \class DebayerCpu -+ * \brief Class for debayering on the CPU -+ * -+ * Implementation for CPU based debayering -+ */ -+class DebayerCpu : public Debayer, public Object -+{ -+public: -+ /* -+ * FIXME this should be a plain (implementation independent) SwStats -+ * this can be fixed once getStats() is dropped. -+ */ -+ /** -+ * \brief Constructs a DebayerCpu object. -+ * \param[in] stats Pointer to the stats object to use. -+ */ -+ DebayerCpu(std::unique_ptr stats); -+ ~DebayerCpu(); -+ -+ /* -+ * Setup the Debayer object according to the passed in parameters. -+ * Return 0 on success, a negative errno value on failure -+ * (unsupported parameters). -+ */ -+ int configure(const StreamConfiguration &inputCfg, -+ const std::vector> &outputCfgs); -+ -+ /* -+ * Get width and height at which the bayer-pattern repeats. -+ * Return pattern-size or an empty Size for an unsupported inputFormat. -+ */ -+ Size patternSize(PixelFormat inputFormat); -+ -+ std::vector formats(PixelFormat input); -+ std::tuple -+ strideAndFrameSize(const PixelFormat &outputFormat, const Size &size); -+ -+ void process(FrameBuffer *input, FrameBuffer *output, DebayerParams params); -+ -+ /** -+ * \brief Get the file descriptor for the statistics. -+ * -+ * \return the file descriptor pointing to the statistics. -+ */ -+ const SharedFD &getStatsFD() { return stats_->getStatsFD(); } -+ -+ /** -+ * \brief Get the output frame size. -+ * -+ * \return The output frame size. -+ */ -+ unsigned int frameSize() { return outputConfig_.frameSize; } -+private: -+ void initLinePointers(const uint8_t *linePointers[], const uint8_t *src); -+ void shiftLinePointers(const uint8_t *linePointers[], const uint8_t *src); -+ void process2(const uint8_t *src, uint8_t *dst); -+ void process4(const uint8_t *src, uint8_t *dst); -+ /* CSI-2 packed 10-bit raw bayer format (all the 4 orders) */ -+ void debayer10P_BGBG_BGR888(uint8_t *dst, const uint8_t *src[]); -+ void debayer10P_GRGR_BGR888(uint8_t *dst, const uint8_t *src[]); -+ void debayer10P_GBGB_BGR888(uint8_t *dst, const uint8_t *src[]); -+ void debayer10P_RGRG_BGR888(uint8_t *dst, const uint8_t *src[]); -+ -+ typedef void (DebayerCpu::*debayerFn)(uint8_t *dst, const uint8_t *src[]); -+ -+ struct DebayerInputConfig { -+ Size patternSize; -+ unsigned int bpp; /* Memory used per pixel, not precision */ -+ unsigned int stride; -+ std::vector outputFormats; -+ }; -+ -+ struct DebayerOutputConfig { -+ unsigned int bpp; /* Memory used per pixel, not precision */ -+ unsigned int stride; -+ unsigned int frameSize; -+ }; -+ -+ int getInputConfig(PixelFormat inputFormat, DebayerInputConfig &config); -+ int getOutputConfig(PixelFormat outputFormat, DebayerOutputConfig &config); -+ int setDebayerFunctions(PixelFormat inputFormat, PixelFormat outputFormat); -+ -+ uint8_t gamma_[1024]; -+ uint8_t red_[256]; -+ uint8_t green_[256]; -+ uint8_t blue_[256]; -+ debayerFn debayer0_; -+ debayerFn debayer1_; -+ debayerFn debayer2_; -+ debayerFn debayer3_; -+ Rectangle window_; -+ DebayerInputConfig inputConfig_; -+ DebayerOutputConfig outputConfig_; -+ std::unique_ptr stats_; -+ uint8_t *lineBuffers_[5]; -+ unsigned int lineBufferIndex_; -+ bool enableInputMemcpy_; -+ float gamma_correction_; -+ int measuredFrames_; -+ int64_t frameProcessTime_; -+ /* Skip 30 frames for things to stabilize then measure 30 frames */ -+ static const int framesToSkip = 30; -+ static const int framesToMeasure = 60; -+}; -+ -+} /* namespace libcamera */ -diff --git a/include/libcamera/internal/software_isp/meson.build b/include/libcamera/internal/software_isp/meson.build -index 7e40925e..b5a0d737 100644 ---- a/include/libcamera/internal/software_isp/meson.build -+++ b/include/libcamera/internal/software_isp/meson.build -@@ -2,6 +2,7 @@ - - libcamera_internal_headers += files([ - 'debayer.h', -+ 'debayer_cpu.h', - 'debayer_params.h', - 'swisp_stats.h', - 'swstats.h', -diff --git a/src/libcamera/software_isp/debayer_cpu.cpp b/src/libcamera/software_isp/debayer_cpu.cpp -new file mode 100644 -index 00000000..e0c3c658 ---- /dev/null -+++ b/src/libcamera/software_isp/debayer_cpu.cpp -@@ -0,0 +1,528 @@ -+/* SPDX-License-Identifier: LGPL-2.1-or-later */ -+/* -+ * Copyright (C) 2023, Linaro Ltd -+ * Copyright (C) 2023, Red Hat Inc. -+ * -+ * Authors: -+ * Hans de Goede -+ * -+ * debayer_cpu.cpp - CPU based debayering class -+ */ -+ -+#include "libcamera/internal/software_isp/debayer_cpu.h" -+ -+#include -+#include -+#include -+ -+#include -+ -+#include "libcamera/internal/bayer_format.h" -+#include "libcamera/internal/framebuffer.h" -+#include "libcamera/internal/mapped_framebuffer.h" -+ -+namespace libcamera { -+ -+DebayerCpu::DebayerCpu(std::unique_ptr stats) -+ : stats_(std::move(stats)), gamma_correction_(1.0) -+{ -+#ifdef __x86_64__ -+ enableInputMemcpy_ = false; -+#else -+ enableInputMemcpy_ = true; -+#endif -+ /* Initialize gamma to 1.0 curve */ -+ for (int i = 0; i < 1024; i++) -+ gamma_[i] = i / 4; -+ -+ for (int i = 0; i < 5; i++) -+ lineBuffers_[i] = NULL; -+} -+ -+DebayerCpu::~DebayerCpu() -+{ -+ for (int i = 0; i < 5; i++) -+ free(lineBuffers_[i]); -+} -+ -+// RGR -+// GBG -+// RGR -+#define BGGR_BGR888(p, n, div) \ -+ *dst++ = blue_[curr[x] / (div)]; \ -+ *dst++ = green_[(prev[x] + curr[x - p] + curr[x + n] + next[x]) / (4 * (div))]; \ -+ *dst++ = red_[(prev[x - p] + prev[x + n] + next[x - p] + next[x + n]) / (4 * (div))]; \ -+ x++; -+ -+// GBG -+// RGR -+// GBG -+#define GRBG_BGR888(p, n, div) \ -+ *dst++ = blue_[(prev[x] + next[x]) / (2 * (div))]; \ -+ *dst++ = green_[curr[x] / (div)]; \ -+ *dst++ = red_[(curr[x - p] + curr[x + n]) / (2 * (div))]; \ -+ x++; -+ -+// GRG -+// BGB -+// GRG -+#define GBRG_BGR888(p, n, div) \ -+ *dst++ = blue_[(curr[x - p] + curr[x + n]) / (2 * (div))]; \ -+ *dst++ = green_[curr[x] / (div)]; \ -+ *dst++ = red_[(prev[x] + next[x]) / (2 * (div))]; \ -+ x++; -+ -+// BGB -+// GRG -+// BGB -+#define RGGB_BGR888(p, n, div) \ -+ *dst++ = blue_[(prev[x - p] + prev[x + n] + next[x - p] + next[x + n]) / (4 * (div))]; \ -+ *dst++ = green_[(prev[x] + curr[x - p] + curr[x + n] + next[x]) / (4 * (div))]; \ -+ *dst++ = red_[curr[x] / (div)]; \ -+ x++; -+ -+void DebayerCpu::debayer10P_BGBG_BGR888(uint8_t *dst, const uint8_t *src[]) -+{ -+ const int width_in_bytes = window_.width * 5 / 4; -+ const uint8_t *prev = (const uint8_t *)src[0]; -+ const uint8_t *curr = (const uint8_t *)src[1]; -+ const uint8_t *next = (const uint8_t *)src[2]; -+ -+ /* -+ * For the first pixel getting a pixel from the previous column uses -+ * x - 2 to skip the 5th byte with least-significant bits for 4 pixels. -+ * Same for last pixel (uses x + 2) and looking at the next column. -+ * x++ in the for-loop skips the 5th byte with 4 x 2 lsb-s for 10bit packed. -+ */ -+ for (int x = 0; x < width_in_bytes; x++) { -+ /* Even pixel */ -+ BGGR_BGR888(2, 1, 1) -+ /* Odd pixel BGGR -> GBRG */ -+ GBRG_BGR888(1, 1, 1) -+ /* Same thing for next 2 pixels */ -+ BGGR_BGR888(1, 1, 1) -+ GBRG_BGR888(1, 2, 1) -+ } -+} -+ -+void DebayerCpu::debayer10P_GRGR_BGR888(uint8_t *dst, const uint8_t *src[]) -+{ -+ const int width_in_bytes = window_.width * 5 / 4; -+ const uint8_t *prev = (const uint8_t *)src[0]; -+ const uint8_t *curr = (const uint8_t *)src[1]; -+ const uint8_t *next = (const uint8_t *)src[2]; -+ -+ for (int x = 0; x < width_in_bytes; x++) { -+ /* Even pixel */ -+ GRBG_BGR888(2, 1, 1) -+ /* Odd pixel GRBG -> RGGB */ -+ RGGB_BGR888(1, 1, 1) -+ /* Same thing for next 2 pixels */ -+ GRBG_BGR888(1, 1, 1) -+ RGGB_BGR888(1, 2, 1) -+ } -+} -+ -+void DebayerCpu::debayer10P_GBGB_BGR888(uint8_t *dst, const uint8_t *src[]) -+{ -+ const int width_in_bytes = window_.width * 5 / 4; -+ const uint8_t *prev = (const uint8_t *)src[0]; -+ const uint8_t *curr = (const uint8_t *)src[1]; -+ const uint8_t *next = (const uint8_t *)src[2]; -+ -+ for (int x = 0; x < width_in_bytes; x++) { -+ /* Even pixel */ -+ GBRG_BGR888(2, 1, 1) -+ /* Odd pixel GBGR -> BGGR */ -+ BGGR_BGR888(1, 1, 1) -+ /* Same thing for next 2 pixels */ -+ GBRG_BGR888(1, 1, 1) -+ BGGR_BGR888(1, 2, 1) -+ } -+} -+ -+void DebayerCpu::debayer10P_RGRG_BGR888(uint8_t *dst, const uint8_t *src[]) -+{ -+ const int width_in_bytes = window_.width * 5 / 4; -+ const uint8_t *prev = (const uint8_t *)src[0]; -+ const uint8_t *curr = (const uint8_t *)src[1]; -+ const uint8_t *next = (const uint8_t *)src[2]; -+ -+ for (int x = 0; x < width_in_bytes; x++) { -+ /* Even pixel */ -+ RGGB_BGR888(2, 1, 1) -+ /* Odd pixel RGGB -> GRBG*/ -+ GRBG_BGR888(1, 1, 1) -+ /* Same thing for next 2 pixels */ -+ RGGB_BGR888(1, 1, 1) -+ GRBG_BGR888(1, 2, 1) -+ } -+} -+ -+static bool isStandardBayerOrder(BayerFormat::Order order) -+{ -+ return order == BayerFormat::BGGR || order == BayerFormat::GBRG || -+ order == BayerFormat::GRBG || order == BayerFormat::RGGB; -+} -+ -+int DebayerCpu::getInputConfig(PixelFormat inputFormat, DebayerInputConfig &config) -+{ -+ BayerFormat bayerFormat = -+ BayerFormat::fromPixelFormat(inputFormat); -+ -+ if (bayerFormat.bitDepth == 10 && -+ bayerFormat.packing == BayerFormat::Packing::CSI2 && -+ isStandardBayerOrder(bayerFormat.order)) { -+ config.bpp = 10; -+ config.patternSize.width = 4; /* 5 bytes per *4* pixels */ -+ config.patternSize.height = 2; -+ config.outputFormats = std::vector({ 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> &outputCfgs) -+{ -+ if (getInputConfig(inputCfg.pixelFormat, inputConfig_) != 0) -+ return -EINVAL; -+ -+ if (stats_->configure(inputCfg) != 0) -+ return -EINVAL; -+ -+ const Size &stats_pattern_size = stats_->patternSize(); -+ if (inputConfig_.patternSize.width != stats_pattern_size.width || -+ inputConfig_.patternSize.height != stats_pattern_size.height) { -+ LOG(Debayer, Error) -+ << "mismatching stats and debayer pattern sizes for " -+ << inputCfg.pixelFormat.toString(); -+ return -EINVAL; -+ } -+ -+ inputConfig_.stride = inputCfg.stride; -+ -+ if (outputCfgs.size() != 1) { -+ LOG(Debayer, Error) -+ << "Unsupported number of output streams: " -+ << outputCfgs.size(); -+ return -EINVAL; -+ } -+ -+ const StreamConfiguration &outputCfg = outputCfgs[0]; -+ SizeRange outSizeRange = sizes(inputCfg.pixelFormat, inputCfg.size); -+ std::tie(outputConfig_.stride, outputConfig_.frameSize) = -+ strideAndFrameSize(outputCfg.pixelFormat, outputCfg.size); -+ -+ if (!outSizeRange.contains(outputCfg.size) || outputConfig_.stride != outputCfg.stride) { -+ LOG(Debayer, Error) -+ << "Invalid output size/stride: " -+ << "\n " << outputCfg.size << " (" << outSizeRange << ")" -+ << "\n " << outputCfg.stride << " (" << outputConfig_.stride << ")"; -+ return -EINVAL; -+ } -+ -+ if (setDebayerFunctions(inputCfg.pixelFormat, outputCfg.pixelFormat) != 0) -+ return -EINVAL; -+ -+ window_.x = ((inputCfg.size.width - outputCfg.size.width) / 2) & -+ ~(inputConfig_.patternSize.width - 1); -+ window_.y = ((inputCfg.size.height - outputCfg.size.height) / 2) & -+ ~(inputConfig_.patternSize.height - 1); -+ window_.width = outputCfg.size.width; -+ window_.height = outputCfg.size.height; -+ -+ /* Don't pass x,y since process() already adjusts src before passing it */ -+ stats_->setWindow(Rectangle(window_.size())); -+ -+ for (unsigned int i = 0; -+ i < (inputConfig_.patternSize.height + 1) && enableInputMemcpy_; -+ i++) { -+ /* pad with patternSize.Width on both left and right side */ -+ size_t lineLength = (window_.width + 2 * inputConfig_.patternSize.width) * -+ inputConfig_.bpp / 8; -+ -+ free(lineBuffers_[i]); -+ lineBuffers_[i] = (uint8_t *)malloc(lineLength); -+ if (!lineBuffers_[i]) -+ return -ENOMEM; -+ } -+ -+ measuredFrames_ = 0; -+ frameProcessTime_ = 0; -+ -+ return 0; -+} -+ -+Size DebayerCpu::patternSize(PixelFormat inputFormat) -+{ -+ DebayerCpu::DebayerInputConfig config; -+ -+ if (getInputConfig(inputFormat, config) != 0) -+ return {}; -+ -+ return config.patternSize; -+} -+ -+std::vector DebayerCpu::formats(PixelFormat inputFormat) -+{ -+ DebayerCpu::DebayerInputConfig config; -+ -+ if (getInputConfig(inputFormat, config) != 0) -+ return std::vector(); -+ -+ return config.outputFormats; -+} -+ -+std::tuple -+DebayerCpu::strideAndFrameSize(const PixelFormat &outputFormat, const Size &size) -+{ -+ DebayerCpu::DebayerOutputConfig config; -+ -+ if (getOutputConfig(outputFormat, config) != 0) -+ return std::make_tuple(0, 0); -+ -+ /* round up to multiple of 8 for 64 bits alignment */ -+ unsigned int stride = (size.width * config.bpp / 8 + 7) & ~7; -+ -+ return std::make_tuple(stride, stride * size.height); -+} -+ -+void DebayerCpu::initLinePointers(const uint8_t *linePointers[], const uint8_t *src) -+{ -+ const int patternHeight = inputConfig_.patternSize.height; -+ -+ for (int i = 0; i < patternHeight; i++) -+ linePointers[i + 1] = src + -+ (-patternHeight / 2 + i) * (int)inputConfig_.stride; -+ -+ if (!enableInputMemcpy_) -+ return; -+ -+ for (int i = 0; i < patternHeight; i++) { -+ /* pad with patternSize.Width on both left and right side */ -+ size_t lineLength = (window_.width + 2 * inputConfig_.patternSize.width) * -+ inputConfig_.bpp / 8; -+ int padding = inputConfig_.patternSize.width * inputConfig_.bpp / 8; -+ -+ memcpy(lineBuffers_[i], linePointers[i + 1] - padding, lineLength); -+ linePointers[i + 1] = lineBuffers_[i] + padding; -+ } -+ -+ /* Point lineBufferIndex_ to first unused lineBuffer */ -+ lineBufferIndex_ = patternHeight; -+} -+ -+void DebayerCpu::shiftLinePointers(const uint8_t *linePointers[], const uint8_t *src) -+{ -+ const int patternHeight = inputConfig_.patternSize.height; -+ -+ for (int i = 0; i < patternHeight; i++) -+ linePointers[i] = linePointers[i + 1]; -+ -+ linePointers[patternHeight] = src + -+ (patternHeight / 2) * (int)inputConfig_.stride; -+ -+ if (!enableInputMemcpy_) -+ return; -+ -+ size_t lineLength = (window_.width + 2 * inputConfig_.patternSize.width) * -+ inputConfig_.bpp / 8; -+ int padding = inputConfig_.patternSize.width * inputConfig_.bpp / 8; -+ memcpy(lineBuffers_[lineBufferIndex_], linePointers[patternHeight] - padding, lineLength); -+ linePointers[patternHeight] = lineBuffers_[lineBufferIndex_] + padding; -+ -+ lineBufferIndex_ = (lineBufferIndex_ + 1) % (patternHeight + 1); -+} -+ -+void DebayerCpu::process2(const uint8_t *src, uint8_t *dst) -+{ -+ const unsigned int y_end = window_.y + window_.height; -+ const uint8_t *linePointers[3]; -+ -+ /* Adjust src to top left corner of the window */ -+ src += window_.y * inputConfig_.stride + window_.x * inputConfig_.bpp / 8; -+ -+ initLinePointers(linePointers, src); -+ -+ for (unsigned int y = window_.y; y < y_end; y += 2) { -+ shiftLinePointers(linePointers, src); -+ stats_->processLine0(y, linePointers); -+ (this->*debayer0_)(dst, linePointers); -+ src += inputConfig_.stride; -+ dst += outputConfig_.stride; -+ -+ shiftLinePointers(linePointers, src); -+ (this->*debayer1_)(dst, linePointers); -+ src += inputConfig_.stride; -+ dst += outputConfig_.stride; -+ } -+} -+ -+void DebayerCpu::process4(const uint8_t *src, uint8_t *dst) -+{ -+ const unsigned int y_end = window_.y + window_.height; -+ const uint8_t *linePointers[5]; -+ -+ /* Adjust src to top left corner of the window */ -+ src += window_.y * inputConfig_.stride + window_.x * inputConfig_.bpp / 8; -+ -+ initLinePointers(linePointers, src); -+ -+ for (unsigned int y = window_.y; y < y_end; y += 4) { -+ shiftLinePointers(linePointers, src); -+ stats_->processLine0(y, linePointers); -+ (this->*debayer0_)(dst, linePointers); -+ src += inputConfig_.stride; -+ dst += outputConfig_.stride; -+ -+ shiftLinePointers(linePointers, src); -+ (this->*debayer1_)(dst, linePointers); -+ src += inputConfig_.stride; -+ dst += outputConfig_.stride; -+ -+ shiftLinePointers(linePointers, src); -+ stats_->processLine2(y, linePointers); -+ (this->*debayer2_)(dst, linePointers); -+ src += inputConfig_.stride; -+ dst += outputConfig_.stride; -+ -+ shiftLinePointers(linePointers, src); -+ (this->*debayer3_)(dst, linePointers); -+ src += inputConfig_.stride; -+ dst += outputConfig_.stride; -+ } -+} -+ -+static inline int64_t timeDiff(timespec &after, timespec &before) -+{ -+ return (after.tv_sec - before.tv_sec) * 1000000000LL + -+ (int64_t)after.tv_nsec - (int64_t)before.tv_nsec; -+} -+ -+void DebayerCpu::process(FrameBuffer *input, FrameBuffer *output, DebayerParams params) -+{ -+ timespec frameStartTime; -+ -+ if (measuredFrames_ < DebayerCpu::framesToMeasure) { -+ frameStartTime = {}; -+ clock_gettime(CLOCK_MONOTONIC_RAW, &frameStartTime); -+ } -+ -+ /* Apply DebayerParams */ -+ if (params.gamma != gamma_correction_) { -+ for (int i = 0; i < 1024; i++) -+ gamma_[i] = 255 * powf(i / 1023.0, params.gamma); -+ -+ gamma_correction_ = params.gamma; -+ } -+ -+ for (int i = 0; i < 256; i++) { -+ int idx; -+ -+ /* Apply gamma after gain! */ -+ idx = std::min({ i * params.gainR / 64U, 1023U }); -+ red_[i] = gamma_[idx]; -+ -+ idx = std::min({ i * params.gainG / 64U, 1023U }); -+ green_[i] = gamma_[idx]; -+ -+ idx = std::min({ i * params.gainB / 64U, 1023U }); -+ blue_[i] = gamma_[idx]; -+ } -+ -+ /* Copy metadata from the input buffer */ -+ FrameMetadata &metadata = output->_d()->metadata(); -+ metadata.status = input->metadata().status; -+ metadata.sequence = input->metadata().sequence; -+ metadata.timestamp = input->metadata().timestamp; -+ -+ MappedFrameBuffer in(input, MappedFrameBuffer::MapFlag::Read); -+ MappedFrameBuffer out(output, MappedFrameBuffer::MapFlag::Write); -+ if (!in.isValid() || !out.isValid()) { -+ LOG(Debayer, Error) << "mmap-ing buffer(s) failed"; -+ metadata.status = FrameMetadata::FrameError; -+ return; -+ } -+ -+ stats_->startFrame(); -+ -+ if (inputConfig_.patternSize.height == 2) -+ process2(in.planes()[0].data(), out.planes()[0].data()); -+ else -+ process4(in.planes()[0].data(), out.planes()[0].data()); -+ -+ metadata.planes()[0].bytesused = out.planes()[0].size(); -+ -+ /* Measure before emitting signals */ -+ if (measuredFrames_ < DebayerCpu::framesToMeasure && -+ ++measuredFrames_ > DebayerCpu::framesToSkip) { -+ timespec frameEndTime = {}; -+ clock_gettime(CLOCK_MONOTONIC_RAW, &frameEndTime); -+ frameProcessTime_ += timeDiff(frameEndTime, frameStartTime); -+ if (measuredFrames_ == DebayerCpu::framesToMeasure) { -+ const int measuredFrames = DebayerCpu::framesToMeasure - -+ DebayerCpu::framesToSkip; -+ LOG(Debayer, Info) -+ << "Processed " << measuredFrames -+ << " frames in " << frameProcessTime_ / 1000 << "us, " -+ << frameProcessTime_ / (1000 * measuredFrames) -+ << " us/frame"; -+ } -+ } -+ -+ stats_->finishFrame(); -+ outputBufferReady.emit(output); -+ inputBufferReady.emit(input); -+} -+ -+} /* namespace libcamera */ -diff --git a/src/libcamera/software_isp/meson.build b/src/libcamera/software_isp/meson.build -index d4ae5ac7..6d7a44d7 100644 ---- a/src/libcamera/software_isp/meson.build -+++ b/src/libcamera/software_isp/meson.build -@@ -2,6 +2,7 @@ - - libcamera_sources += files([ - 'debayer.cpp', -+ 'debayer_cpu.cpp', - 'swstats.cpp', - 'swstats_cpu.cpp', - ]) --- -2.43.0 - diff --git a/modules/nixos/ipu6-softisp/libcamera/0011-libcamera-ipa-add-Soft-IPA-common-files.patch b/modules/nixos/ipu6-softisp/libcamera/0011-libcamera-ipa-add-Soft-IPA-common-files.patch deleted file mode 100644 index d5fd94f..0000000 --- a/modules/nixos/ipu6-softisp/libcamera/0011-libcamera-ipa-add-Soft-IPA-common-files.patch +++ /dev/null @@ -1,256 +0,0 @@ -From 05b353f1e45f2af0d0989261210b4bedef5144de Mon Sep 17 00:00:00 2001 -From: Andrey Konovalov -Date: Mon, 11 Dec 2023 23:41:58 +0300 -Subject: [PATCH 11/25] libcamera: ipa: add Soft IPA common files - -Define the Soft IPA main and event interfaces, add IPASoftBase -class the Soft IPA implementation inherit from. - -Signed-off-by: Andrey Konovalov -Signed-off-by: Hans de Goede -Tested-by: Bryan O'Donoghue # sc8280xp Lenovo x13s -Tested-by: Pavel Machek ---- - Documentation/Doxyfile.in | 1 + - include/libcamera/ipa/meson.build | 1 + - include/libcamera/ipa/soft.mojom | 29 ++++++++++++ - src/ipa/simple/common/meson.build | 17 +++++++ - src/ipa/simple/common/soft_base.cpp | 73 +++++++++++++++++++++++++++++ - src/ipa/simple/common/soft_base.h | 50 ++++++++++++++++++++ - src/ipa/simple/meson.build | 3 ++ - 7 files changed, 174 insertions(+) - create mode 100644 include/libcamera/ipa/soft.mojom - create mode 100644 src/ipa/simple/common/meson.build - create mode 100644 src/ipa/simple/common/soft_base.cpp - create mode 100644 src/ipa/simple/common/soft_base.h - create mode 100644 src/ipa/simple/meson.build - -diff --git a/Documentation/Doxyfile.in b/Documentation/Doxyfile.in -index a86ea6c1..2be8d47b 100644 ---- a/Documentation/Doxyfile.in -+++ b/Documentation/Doxyfile.in -@@ -44,6 +44,7 @@ EXCLUDE = @TOP_SRCDIR@/include/libcamera/base/span.h \ - @TOP_SRCDIR@/src/libcamera/pipeline/ \ - @TOP_SRCDIR@/src/libcamera/tracepoints.cpp \ - @TOP_BUILDDIR@/include/libcamera/internal/tracepoints.h \ -+ @TOP_BUILDDIR@/include/libcamera/ipa/soft_ipa_interface.h \ - @TOP_BUILDDIR@/src/libcamera/proxy/ - - EXCLUDE_PATTERNS = @TOP_BUILDDIR@/include/libcamera/ipa/*_serializer.h \ -diff --git a/include/libcamera/ipa/meson.build b/include/libcamera/ipa/meson.build -index f3b4881c..894e38a6 100644 ---- a/include/libcamera/ipa/meson.build -+++ b/include/libcamera/ipa/meson.build -@@ -65,6 +65,7 @@ pipeline_ipa_mojom_mapping = { - 'ipu3': 'ipu3.mojom', - 'rkisp1': 'rkisp1.mojom', - 'rpi/vc4': 'raspberrypi.mojom', -+ 'simple/simple': 'soft.mojom', - 'vimc': 'vimc.mojom', - } - -diff --git a/include/libcamera/ipa/soft.mojom b/include/libcamera/ipa/soft.mojom -new file mode 100644 -index 00000000..2dae652b ---- /dev/null -+++ b/include/libcamera/ipa/soft.mojom -@@ -0,0 +1,29 @@ -+/* SPDX-License-Identifier: LGPL-2.1-or-later */ -+ -+/* -+ * \todo Document the interface and remove the related EXCLUDE_PATTERNS entry. -+ * \todo Add a way to tell SoftIPA the list of params SoftISP accepts? -+ */ -+ -+module ipa.soft; -+ -+import "include/libcamera/ipa/core.mojom"; -+ -+interface IPASoftInterface { -+ init(libcamera.IPASettings settings, -+ libcamera.SharedFD fdStats, -+ libcamera.SharedFD fdParams, -+ libcamera.ControlInfoMap sensorCtrlInfoMap) -+ => (int32 ret); -+ start() => (int32 ret); -+ stop(); -+ configure(libcamera.ControlInfoMap sensorCtrlInfoMap) -+ => (int32 ret); -+ -+ [async] processStats(libcamera.ControlList sensorControls); -+}; -+ -+interface IPASoftEventInterface { -+ setSensorControls(libcamera.ControlList sensorControls); -+ setIspParams(int32 dummy); -+}; -diff --git a/src/ipa/simple/common/meson.build b/src/ipa/simple/common/meson.build -new file mode 100644 -index 00000000..023e617b ---- /dev/null -+++ b/src/ipa/simple/common/meson.build -@@ -0,0 +1,17 @@ -+# SPDX-License-Identifier: CC0-1.0 -+ -+soft_ipa_common_sources = files([ -+ 'soft_base.cpp', -+]) -+ -+soft_ipa_common_includes = [ -+ include_directories('..'), -+] -+ -+soft_ipa_common_deps = [ -+ libcamera_private, -+] -+ -+soft_ipa_common_lib = static_library('soft_ipa_common', soft_ipa_common_sources, -+ include_directories : soft_ipa_common_includes, -+ dependencies : soft_ipa_common_deps) -diff --git a/src/ipa/simple/common/soft_base.cpp b/src/ipa/simple/common/soft_base.cpp -new file mode 100644 -index 00000000..b4ed9023 ---- /dev/null -+++ b/src/ipa/simple/common/soft_base.cpp -@@ -0,0 +1,73 @@ -+/* SPDX-License-Identifier: LGPL-2.1-or-later */ -+/* -+ * Copyright (C) 2023, Linaro Ltd -+ * -+ * soft-base.cpp - Software IPA base class -+ */ -+ -+#include "soft_base.h" -+ -+#include -+ -+#include -+#include -+ -+#include -+ -+namespace libcamera { -+ -+LOG_DEFINE_CATEGORY(IPASoft) -+ -+namespace ipa::soft { -+ -+IPASoftBase::IPASoftBase() -+{ -+} -+ -+IPASoftBase::~IPASoftBase() -+{ -+} -+ -+int IPASoftBase::init([[maybe_unused]] const IPASettings &settings, -+ const SharedFD &fdStats, -+ const SharedFD &fdParams, -+ const ControlInfoMap &sensorInfoMap) -+{ -+ fdStats_ = std::move(fdStats); -+ if (!fdStats_.isValid()) { -+ LOG(IPASoft, Error) << "Invalid Statistics handle"; -+ return -ENODEV; -+ } -+ -+ fdParams_ = std::move(fdParams); -+ if (!fdParams_.isValid()) { -+ LOG(IPASoft, Error) << "Invalid Parameters handle"; -+ return -ENODEV; -+ } -+ -+ return platformInit(sensorInfoMap); -+} -+ -+int IPASoftBase::configure(const ControlInfoMap &sensorInfoMap) -+{ -+ return platformConfigure(sensorInfoMap); -+} -+ -+int IPASoftBase::start() -+{ -+ return platformStart(); -+} -+ -+void IPASoftBase::stop() -+{ -+ return platformStop(); -+} -+ -+void IPASoftBase::processStats(const ControlList &sensorControls) -+{ -+ return platformProcessStats(sensorControls); -+} -+ -+} /* namespace ipa::soft */ -+ -+} /* namespace libcamera */ -diff --git a/src/ipa/simple/common/soft_base.h b/src/ipa/simple/common/soft_base.h -new file mode 100644 -index 00000000..85c98702 ---- /dev/null -+++ b/src/ipa/simple/common/soft_base.h -@@ -0,0 +1,50 @@ -+/* SPDX-License-Identifier: LGPL-2.1-or-later */ -+/* -+ * Copyright (C) 2023, Linaro Ltd -+ * -+ * soft-base.h - Software IPA base class -+ */ -+#pragma once -+ -+#include -+ -+#include -+ -+#include -+ -+namespace libcamera { -+ -+namespace ipa::soft { -+ -+class IPASoftBase : public ipa::soft::IPASoftInterface -+{ -+public: -+ IPASoftBase(); -+ ~IPASoftBase(); -+ -+ int init(const IPASettings &settings, -+ const SharedFD &fdStats, -+ const SharedFD &fdParams, -+ const ControlInfoMap &sensorInfoMap) override; -+ int configure(const ControlInfoMap &sensorInfoMap) override; -+ -+ int start() override; -+ void stop() override; -+ -+ void processStats(const ControlList &sensorControls) override; -+ -+protected: -+ SharedFD fdStats_; -+ SharedFD fdParams_; -+ -+private: -+ virtual int platformInit(const ControlInfoMap &sensorInfoMap) = 0; -+ virtual int platformConfigure(const ControlInfoMap &sensorInfoMap) = 0; -+ virtual int platformStart() = 0; -+ virtual void platformStop() = 0; -+ virtual void platformProcessStats(const ControlList &sensorControls) = 0; -+}; -+ -+} /* namespace ipa::soft */ -+ -+} /* namespace libcamera */ -diff --git a/src/ipa/simple/meson.build b/src/ipa/simple/meson.build -new file mode 100644 -index 00000000..9688bbdb ---- /dev/null -+++ b/src/ipa/simple/meson.build -@@ -0,0 +1,3 @@ -+# SPDX-License-Identifier: CC0-1.0 -+ -+subdir('common') --- -2.43.0 - diff --git a/modules/nixos/ipu6-softisp/libcamera/0012-libcamera-ipa-Soft-IPA-add-a-Simple-Soft-IPA-impleme.patch b/modules/nixos/ipu6-softisp/libcamera/0012-libcamera-ipa-Soft-IPA-add-a-Simple-Soft-IPA-impleme.patch deleted file mode 100644 index b7cb274..0000000 --- a/modules/nixos/ipu6-softisp/libcamera/0012-libcamera-ipa-Soft-IPA-add-a-Simple-Soft-IPA-impleme.patch +++ /dev/null @@ -1,407 +0,0 @@ -From c0886381a2bbe494b900d699a3858573316059b2 Mon Sep 17 00:00:00 2001 -From: Andrey Konovalov -Date: Mon, 11 Dec 2023 23:47:47 +0300 -Subject: [PATCH 12/25] libcamera: ipa: Soft IPA: add a Simple Soft IPA - implementation - -Auto exposure/gain and AWB implementation by Dennis, Toon and Martti. - -Co-authored-by: Dennis Bonke -Signed-off-by: Dennis Bonke -Co-authored-by: Marttico -Signed-off-by: Marttico -Co-authored-by: Toon Langendam -Signed-off-by: Toon Langendam -Signed-off-by: Andrey Konovalov -Signed-off-by: Hans de Goede -Tested-by: Bryan O'Donoghue # sc8280xp Lenovo x13s -Tested-by: Pavel Machek ---- - meson_options.txt | 3 +- - src/ipa/simple/meson.build | 9 + - src/ipa/simple/simple/data/meson.build | 9 + - src/ipa/simple/simple/data/soft.conf | 3 + - src/ipa/simple/simple/meson.build | 26 +++ - src/ipa/simple/simple/soft_simple.cpp | 273 +++++++++++++++++++++++++ - 6 files changed, 322 insertions(+), 1 deletion(-) - create mode 100644 src/ipa/simple/simple/data/meson.build - create mode 100644 src/ipa/simple/simple/data/soft.conf - create mode 100644 src/ipa/simple/simple/meson.build - create mode 100644 src/ipa/simple/simple/soft_simple.cpp - -diff --git a/meson_options.txt b/meson_options.txt -index 5fdc7be8..8ec08658 100644 ---- a/meson_options.txt -+++ b/meson_options.txt -@@ -27,7 +27,7 @@ option('gstreamer', - - option('ipas', - type : 'array', -- choices : ['ipu3', 'rkisp1', 'rpi/vc4', 'vimc'], -+ choices : ['ipu3', 'rkisp1', 'rpi/vc4', 'simple/simple', 'vimc'], - description : 'Select which IPA modules to build') - - option('lc-compliance', -@@ -46,6 +46,7 @@ option('pipelines', - 'rkisp1', - 'rpi/vc4', - 'simple', -+ 'simple/simple', - 'uvcvideo', - 'vimc' - ], -diff --git a/src/ipa/simple/meson.build b/src/ipa/simple/meson.build -index 9688bbdb..14be5dc2 100644 ---- a/src/ipa/simple/meson.build -+++ b/src/ipa/simple/meson.build -@@ -1,3 +1,12 @@ - # SPDX-License-Identifier: CC0-1.0 - - subdir('common') -+ -+foreach pipeline : pipelines -+ pipeline = pipeline.split('/') -+ if pipeline.length() < 2 or pipeline[0] != 'simple' -+ continue -+ endif -+ -+ subdir(pipeline[1]) -+endforeach -diff --git a/src/ipa/simple/simple/data/meson.build b/src/ipa/simple/simple/data/meson.build -new file mode 100644 -index 00000000..33548cc6 ---- /dev/null -+++ b/src/ipa/simple/simple/data/meson.build -@@ -0,0 +1,9 @@ -+# SPDX-License-Identifier: CC0-1.0 -+ -+conf_files = files([ -+ 'soft.conf', -+]) -+ -+install_data(conf_files, -+ install_dir : ipa_data_dir / 'soft', -+ install_tag : 'runtime') -diff --git a/src/ipa/simple/simple/data/soft.conf b/src/ipa/simple/simple/data/soft.conf -new file mode 100644 -index 00000000..0c70e7c0 ---- /dev/null -+++ b/src/ipa/simple/simple/data/soft.conf -@@ -0,0 +1,3 @@ -+# SPDX-License-Identifier: LGPL-2.1-or-later -+# -+# Dummy configuration file for the soft IPA. -diff --git a/src/ipa/simple/simple/meson.build b/src/ipa/simple/simple/meson.build -new file mode 100644 -index 00000000..8b5d76b5 ---- /dev/null -+++ b/src/ipa/simple/simple/meson.build -@@ -0,0 +1,26 @@ -+# SPDX-License-Identifier: CC0-1.0 -+ -+ipa_name = 'ipa_soft_simple' -+ -+mod = shared_module(ipa_name, -+ ['soft_simple.cpp', libcamera_generated_ipa_headers], -+ name_prefix : '', -+ include_directories : [ipa_includes, libipa_includes, '..'], -+ dependencies : libcamera_private, -+ link_with : libipa, -+ link_whole : soft_ipa_common_lib, -+ install : true, -+ install_dir : ipa_install_dir) -+ -+if ipa_sign_module -+ custom_target(ipa_name + '.so.sign', -+ input : mod, -+ output : ipa_name + '.so.sign', -+ command : [ipa_sign, ipa_priv_key, '@INPUT@', '@OUTPUT@'], -+ install : false, -+ build_by_default : true) -+endif -+ -+subdir('data') -+ -+ipa_names += ipa_name -diff --git a/src/ipa/simple/simple/soft_simple.cpp b/src/ipa/simple/simple/soft_simple.cpp -new file mode 100644 -index 00000000..93fc1545 ---- /dev/null -+++ b/src/ipa/simple/simple/soft_simple.cpp -@@ -0,0 +1,273 @@ -+/* SPDX-License-Identifier: LGPL-2.1-or-later */ -+/* -+ * Copyright (C) 2023, Linaro Ltd -+ * -+ * soft_simple.cpp - Simple Software Image Processing Algorithm module -+ */ -+ -+#include -+ -+#include -+#include -+ -+#include -+ -+#include -+#include -+ -+#include "libcamera/internal/camera_sensor.h" -+#include "libcamera/internal/software_isp/debayer_params.h" -+#include "libcamera/internal/software_isp/swisp_stats.h" -+ -+#include "common/soft_base.h" -+ -+#define EXPOSURE_OPTIMAL_VALUE 2.5 -+#define EXPOSURE_SATISFACTORY_OFFSET 0.2 -+ -+namespace libcamera { -+ -+LOG_DECLARE_CATEGORY(IPASoft) -+ -+namespace ipa::soft { -+ -+class IPASoftSimple final : public IPASoftBase -+{ -+public: -+ IPASoftSimple() -+ : IPASoftBase(), ignore_updates_(0) -+ { -+ } -+ -+ ~IPASoftSimple() -+ { -+ if (stats_) -+ munmap(stats_, sizeof(SwIspStats)); -+ if (params_) -+ munmap(params_, sizeof(DebayerParams)); -+ } -+ -+ int platformInit(const ControlInfoMap &sensorInfoMap) override; -+ int platformConfigure(const ControlInfoMap &sensorInfoMap) override; -+ int platformStart() override; -+ void platformStop() override; -+ void platformProcessStats(const ControlList &sensorControls) override; -+ -+private: -+ void update_exposure(double exposuremsv); -+ -+ DebayerParams *params_; -+ SwIspStats *stats_; -+ int exposure_min_, exposure_max_; -+ int again_min_, again_max_; -+ int again_, exposure_; -+ int ignore_updates_; -+}; -+ -+int IPASoftSimple::platformInit(const ControlInfoMap &sensorInfoMap) -+{ -+ params_ = static_cast(mmap(nullptr, sizeof(DebayerParams), -+ PROT_WRITE, MAP_SHARED, -+ fdParams_.get(), 0)); -+ if (!params_) { -+ LOG(IPASoft, Error) << "Unable to map Parameters"; -+ return -ENODEV; -+ } -+ -+ stats_ = static_cast(mmap(nullptr, sizeof(SwIspStats), -+ PROT_READ, MAP_SHARED, -+ fdStats_.get(), 0)); -+ if (!stats_) { -+ LOG(IPASoft, Error) << "Unable to map Statistics"; -+ return -ENODEV; -+ } -+ -+ if (sensorInfoMap.find(V4L2_CID_EXPOSURE) == sensorInfoMap.end()) { -+ LOG(IPASoft, Error) << "Don't have exposure control"; -+ return -EINVAL; -+ } -+ -+ if (sensorInfoMap.find(V4L2_CID_ANALOGUE_GAIN) == sensorInfoMap.end()) { -+ LOG(IPASoft, Error) << "Don't have gain control"; -+ return -EINVAL; -+ } -+ -+ return 0; -+} -+ -+int IPASoftSimple::platformConfigure(const ControlInfoMap &sensorInfoMap) -+{ -+ const ControlInfo &exposure_info = sensorInfoMap.find(V4L2_CID_EXPOSURE)->second; -+ const ControlInfo &gain_info = sensorInfoMap.find(V4L2_CID_ANALOGUE_GAIN)->second; -+ -+ exposure_min_ = exposure_info.min().get(); -+ if (!exposure_min_) { -+ LOG(IPASoft, Warning) << "Minimum exposure is zero, that can't be linear"; -+ exposure_min_ = 1; -+ } -+ exposure_max_ = exposure_info.max().get(); -+ again_min_ = gain_info.min().get(); -+ if (!again_min_) { -+ LOG(IPASoft, Warning) << "Minimum gain is zero, that can't be linear"; -+ again_min_ = 100; -+ } -+ again_max_ = gain_info.max().get(); -+ -+ LOG(IPASoft, Info) << "Exposure " << exposure_min_ << "-" << exposure_max_ -+ << ", gain " << again_min_ << "-" << again_max_; -+ -+ return 0; -+} -+ -+int IPASoftSimple::platformStart() -+{ -+ return 0; -+} -+ -+void IPASoftSimple::platformStop() -+{ -+} -+ -+void IPASoftSimple::platformProcessStats(const ControlList &sensorControls) -+{ -+ /* -+ * Calculate red and blue gains for AWB. -+ * Clamp max gain at 4.0, this also avoids 0 division. -+ */ -+ if (stats_->sumR_ <= stats_->sumG_ / 4) -+ params_->gainR = 1024; -+ else -+ params_->gainR = 256 * stats_->sumG_ / stats_->sumR_; -+ -+ if (stats_->sumB_ <= stats_->sumG_ / 4) -+ params_->gainB = 1024; -+ else -+ params_->gainB = 256 * stats_->sumG_ / stats_->sumB_; -+ -+ /* Green gain and gamma values are fixed */ -+ params_->gainG = 256; -+ params_->gamma = 0.5; -+ -+ setIspParams.emit(0); -+ -+ /* -+ * AE / AGC, use 2 frames delay to make sure that the exposure and -+ * the gain set have applied to the camera sensor. -+ */ -+ if (ignore_updates_ > 0) { -+ --ignore_updates_; -+ return; -+ } -+ -+ unsigned int denom = 0; -+ unsigned int num = 0; -+ unsigned int y_histogramSmall[5] = {}; -+ -+ for (int i = 0; i < 16; i++) -+ y_histogramSmall[(i - i / 8) / 3] += stats_->y_histogram[i]; -+ -+ for (int i = 0; i < 5; i++) { -+ LOG(IPASoft, Debug) << i << ": " << y_histogramSmall[i]; -+ denom += y_histogramSmall[i]; -+ num += y_histogramSmall[i] * (i + 1); -+ } -+ -+ float exposuremsv = (float)num / denom; -+ -+ /* sanity check */ -+ if (!sensorControls.contains(V4L2_CID_EXPOSURE) || -+ !sensorControls.contains(V4L2_CID_ANALOGUE_GAIN)) { -+ LOG(IPASoft, Error) << "Control(s) missing"; -+ return; -+ } -+ -+ ControlList ctrls(sensorControls); -+ -+ exposure_ = ctrls.get(V4L2_CID_EXPOSURE).get(); -+ again_ = ctrls.get(V4L2_CID_ANALOGUE_GAIN).get(); -+ -+ update_exposure(exposuremsv); -+ -+ ctrls.set(V4L2_CID_EXPOSURE, exposure_); -+ ctrls.set(V4L2_CID_ANALOGUE_GAIN, again_); -+ -+ ignore_updates_ = 2; -+ -+ setSensorControls.emit(ctrls); -+ -+ LOG(IPASoft, Debug) << "exposuremsv " << exposuremsv -+ << " exp " << exposure_ << " again " << again_ -+ << " gain R/B " << params_->gainR << "/" << params_->gainB; -+} -+ -+/* DENOMINATOR of 10 gives ~10% increment/decrement; DENOMINATOR of 5 - about ~20% */ -+#define DENOMINATOR 10 -+#define UP_NUMERATOR (DENOMINATOR + 1) -+#define DOWN_NUMERATOR (DENOMINATOR - 1) -+ -+void IPASoftSimple::update_exposure(double exposuremsv) -+{ -+ int next; -+ -+ if (exposuremsv < EXPOSURE_OPTIMAL_VALUE - EXPOSURE_SATISFACTORY_OFFSET) { -+ next = exposure_ * UP_NUMERATOR / DENOMINATOR; -+ if (next - exposure_ < 1) -+ exposure_ += 1; -+ else -+ exposure_ = next; -+ if (exposure_ >= exposure_max_) { -+ next = again_ * UP_NUMERATOR / DENOMINATOR; -+ if (next - again_ < 1) -+ again_ += 1; -+ else -+ again_ = next; -+ } -+ } -+ -+ if (exposuremsv > EXPOSURE_OPTIMAL_VALUE + EXPOSURE_SATISFACTORY_OFFSET) { -+ if (exposure_ == exposure_max_ && again_ != again_min_) { -+ next = again_ * DOWN_NUMERATOR / DENOMINATOR; -+ if (again_ - next < 1) -+ again_ -= 1; -+ else -+ again_ = next; -+ } else { -+ next = exposure_ * DOWN_NUMERATOR / DENOMINATOR; -+ if (exposure_ - next < 1) -+ exposure_ -= 1; -+ else -+ exposure_ = next; -+ } -+ } -+ -+ if (exposure_ > exposure_max_) -+ exposure_ = exposure_max_; -+ else if (exposure_ < exposure_min_) -+ exposure_ = exposure_min_; -+ -+ if (again_ > again_max_) -+ again_ = again_max_; -+ else if (again_ < again_min_) -+ again_ = again_min_; -+} -+ -+} /* namespace ipa::soft */ -+ -+/* -+ * External IPA module interface -+ */ -+extern "C" { -+const struct IPAModuleInfo ipaModuleInfo = { -+ IPA_MODULE_API_VERSION, -+ 0, -+ "SimplePipelineHandler", -+ "soft/simple", -+}; -+ -+IPAInterface *ipaCreate() -+{ -+ return new ipa::soft::IPASoftSimple(); -+} -+ -+} /* extern "C" */ -+ -+} /* namespace libcamera */ --- -2.43.0 - diff --git a/modules/nixos/ipu6-softisp/libcamera/0013-libcamera-software_isp-add-Simple-SoftwareIsp-implem.patch b/modules/nixos/ipu6-softisp/libcamera/0013-libcamera-software_isp-add-Simple-SoftwareIsp-implem.patch deleted file mode 100644 index 24636ed..0000000 --- a/modules/nixos/ipu6-softisp/libcamera/0013-libcamera-software_isp-add-Simple-SoftwareIsp-implem.patch +++ /dev/null @@ -1,483 +0,0 @@ -From 21f1dd954a44b4e8f81abbfea276e4b60f8a8297 Mon Sep 17 00:00:00 2001 -From: Andrey Konovalov -Date: Thu, 23 Nov 2023 16:47:15 +0300 -Subject: [PATCH 13/25] libcamera: software_isp: add Simple SoftwareIsp - implementation - -The implementation of SoftwareIsp handles creation of Soft IPA -and interactions with it, so that the pipeline handler wouldn't -need to care about the Soft IPA. - -Doxygen documentation by Dennis Bonke. - -Signed-off-by: Andrey Konovalov -Co-authored-by: Dennis Bonke -Signed-off-by: Dennis Bonke -Co-authored-by: Hans de Goede -Signed-off-by: Hans de Goede -Tested-by: Bryan O'Donoghue # sc8280xp Lenovo x13s -Tested-by: Pavel Machek ---- - .../internal/software_isp/meson.build | 1 + - .../internal/software_isp/swisp_simple.h | 163 ++++++++++++ - src/libcamera/software_isp/meson.build | 19 ++ - src/libcamera/software_isp/swisp_simple.cpp | 238 ++++++++++++++++++ - 4 files changed, 421 insertions(+) - create mode 100644 include/libcamera/internal/software_isp/swisp_simple.h - create mode 100644 src/libcamera/software_isp/swisp_simple.cpp - -diff --git a/include/libcamera/internal/software_isp/meson.build b/include/libcamera/internal/software_isp/meson.build -index b5a0d737..cf21ace5 100644 ---- a/include/libcamera/internal/software_isp/meson.build -+++ b/include/libcamera/internal/software_isp/meson.build -@@ -4,6 +4,7 @@ libcamera_internal_headers += files([ - 'debayer.h', - 'debayer_cpu.h', - 'debayer_params.h', -+ 'swisp_simple.h', - 'swisp_stats.h', - 'swstats.h', - 'swstats_cpu.h', -diff --git a/include/libcamera/internal/software_isp/swisp_simple.h b/include/libcamera/internal/software_isp/swisp_simple.h -new file mode 100644 -index 00000000..87613c23 ---- /dev/null -+++ b/include/libcamera/internal/software_isp/swisp_simple.h -@@ -0,0 +1,163 @@ -+/* SPDX-License-Identifier: LGPL-2.1-or-later */ -+/* -+ * Copyright (C) 2023, Linaro Ltd -+ * -+ * swisp_simple.h - Simple software ISP implementation -+ */ -+ -+#pragma once -+ -+#include -+#include -+ -+#include -+ -+#include -+#include -+ -+#include "libcamera/internal/dma_heaps.h" -+#include "libcamera/internal/software_isp.h" -+#include "libcamera/internal/software_isp/debayer_cpu.h" -+ -+namespace libcamera { -+ -+/** -+ * \brief Class for the Simple Software ISP. -+ * -+ * Implementation of the SoftwareIsp interface. -+ */ -+class SwIspSimple : public SoftwareIsp -+{ -+public: -+ /** -+ * \brief Constructor for the SwIspSimple object. -+ * -+ * \param[in] pipe The pipeline handler in use. -+ * \param[in] sensorControls The sensor controls. -+ */ -+ SwIspSimple(PipelineHandler *pipe, const ControlInfoMap &sensorControls); -+ ~SwIspSimple() {} -+ -+ /** -+ * \brief Load a configuration from a file. -+ * \param[in] filename The file to load from. -+ * -+ * \return 0 on success. -+ */ -+ int loadConfiguration([[maybe_unused]] const std::string &filename) { return 0; } -+ -+ /** -+ * \brief Gets if there is a valid debayer object. -+ * -+ * \returns true if there is, false otherwise. -+ */ -+ bool isValid() const; -+ -+ /** -+ * \brief Get the supported output formats. -+ * \param[in] input The input format. -+ * -+ * \return all supported output formats or an empty vector if there are none. -+ */ -+ std::vector formats(PixelFormat input); -+ -+ /** -+ * \brief Get the supported output sizes for the given input format and size. -+ * \param[in] inputFormat The input format. -+ * \param[in] inputSize The input size. -+ * -+ * \return The valid size ranges or an empty range if there are none. -+ */ -+ SizeRange sizes(PixelFormat inputFormat, const Size &inputSize); -+ -+ /** -+ * \brief Get the stride and the frame size. -+ * \param[in] outputFormat The output format. -+ * \param[in] size The output size. -+ * -+ * \return a tuple of the stride and the frame size, or a tuple with 0,0 if there is no valid output config. -+ */ -+ std::tuple -+ strideAndFrameSize(const PixelFormat &outputFormat, const Size &size); -+ -+ /** -+ * \brief Configure the SwIspSimple object according to the passed in parameters. -+ * \param[in] inputCfg The input configuration. -+ * \param[in] outputCfgs The output configurations. -+ * \param[in] sensorControls The sensor controls. -+ * -+ * \return 0 on success, a negative errno on failure. -+ */ -+ int configure(const StreamConfiguration &inputCfg, -+ const std::vector> &outputCfgs, -+ const ControlInfoMap &sensorControls); -+ -+ /** -+ * \brief Exports the buffers for use in processing. -+ * \param[in] output The number of outputs requested. -+ * \param[in] count The number of planes. -+ * \param[out] buffers The exported buffers. -+ * -+ * \return count when successful, a negative return value if an error occurred. -+ */ -+ int exportBuffers(unsigned int output, unsigned int count, -+ std::vector> *buffers); -+ -+ /** -+ * \brief Process the statistics gathered. -+ * \param[in] sensorControls The sensor controls. -+ */ -+ void processStats(const ControlList &sensorControls); -+ -+ /** -+ * \brief Starts the Software ISP worker. -+ * -+ * \return 0 on success, any other value indicates an error. -+ */ -+ int start(); -+ -+ /** -+ * \brief Stops the Software ISP worker. -+ */ -+ void stop(); -+ -+ /** -+ * \brief Queues buffers for processing. -+ * \param[in] input The input framebuffer. -+ * \param[in] outputs The output framebuffers. -+ * -+ * \return 0 on success, a negative errno on failure -+ */ -+ int queueBuffers(FrameBuffer *input, -+ const std::map &outputs); -+ -+ /** -+ * \brief Get the signal for when the sensor controls are set. -+ * -+ * \return The control list of the sensor controls. -+ */ -+ Signal &getSignalSetSensorControls(); -+ -+ /** -+ * \brief Process the input framebuffer. -+ * \param[in] input The input framebuffer. -+ * \param[out] output The output framebuffer. -+ */ -+ void process(FrameBuffer *input, FrameBuffer *output); -+ -+private: -+ void saveIspParams(int dummy); -+ void statsReady(int dummy); -+ void inputReady(FrameBuffer *input); -+ void outputReady(FrameBuffer *output); -+ -+ std::unique_ptr debayer_; -+ Thread ispWorkerThread_; -+ SharedMemObject sharedParams_; -+ DebayerParams debayerParams_; -+ DmaHeap dmaHeap_; -+ -+ std::unique_ptr ipa_; -+}; -+ -+} /* namespace libcamera */ -diff --git a/src/libcamera/software_isp/meson.build b/src/libcamera/software_isp/meson.build -index 6d7a44d7..9464f2fd 100644 ---- a/src/libcamera/software_isp/meson.build -+++ b/src/libcamera/software_isp/meson.build -@@ -6,3 +6,22 @@ libcamera_sources += files([ - 'swstats.cpp', - 'swstats_cpu.cpp', - ]) -+ -+# Software ISP is enabled for 'simple' pipeline handler. -+# The 'pipelines' option value should be -+# 'simple/' e.g.: -+# -Dpipelines=simple/simple -+# The source file should be named swisp_.cpp, -+# e.g. 'swisp_simple.cpp'. -+ -+foreach pipeline : pipelines -+ pipeline = pipeline.split('/') -+ if pipeline.length() == 2 and pipeline[0] == 'simple' -+ libcamera_sources += files([ -+ 'swisp_' + pipeline[1] + '.cpp', -+ ]) -+ # the 'break' below can be removed if/when multiple -+ # Software ISP implementations are allowed in single build -+ break -+ endif -+endforeach -diff --git a/src/libcamera/software_isp/swisp_simple.cpp b/src/libcamera/software_isp/swisp_simple.cpp -new file mode 100644 -index 00000000..0884166e ---- /dev/null -+++ b/src/libcamera/software_isp/swisp_simple.cpp -@@ -0,0 +1,238 @@ -+/* SPDX-License-Identifier: LGPL-2.1-or-later */ -+/* -+ * Copyright (C) 2023, Linaro Ltd -+ * -+ * swisp_simple.cpp - Simple software ISP implementation -+ */ -+ -+#include "libcamera/internal/software_isp/swisp_simple.h" -+ -+#include -+#include -+#include -+ -+#include -+#include -+ -+#include "libcamera/internal/bayer_format.h" -+#include "libcamera/internal/framebuffer.h" -+#include "libcamera/internal/ipa_manager.h" -+#include "libcamera/internal/mapped_framebuffer.h" -+ -+namespace libcamera { -+ -+SwIspSimple::SwIspSimple(PipelineHandler *pipe, const ControlInfoMap &sensorControls) -+ : SoftwareIsp(pipe, sensorControls), debayer_(nullptr), debayerParams_{ 256, 256, 256, 0.5f }, -+ dmaHeap_(DmaHeap::DmaHeapFlag::Cma | DmaHeap::DmaHeapFlag::System) -+{ -+ if (!dmaHeap_.isValid()) { -+ LOG(SoftwareIsp, Error) << "Failed to create DmaHeap object"; -+ return; -+ } -+ -+ sharedParams_ = SharedMemObject("softIsp_params"); -+ if (!sharedParams_.fd().isValid()) { -+ LOG(SoftwareIsp, Error) << "Failed to create shared memory for parameters"; -+ return; -+ } -+ -+ std::unique_ptr stats; -+ -+ stats = std::make_unique(); -+ if (!stats) { -+ LOG(SoftwareIsp, Error) << "Failed to create SwStatsCpu object"; -+ return; -+ } -+ -+ stats->statsReady.connect(this, &SwIspSimple::statsReady); -+ -+ debayer_ = std::make_unique(std::move(stats)); -+ if (!debayer_) { -+ LOG(SoftwareIsp, Error) << "Failed to create DebayerCpu object"; -+ return; -+ } -+ -+ debayer_->inputBufferReady.connect(this, &SwIspSimple::inputReady); -+ debayer_->outputBufferReady.connect(this, &SwIspSimple::outputReady); -+ -+ ipa_ = IPAManager::createIPA(pipe, 0, 0); -+ if (!ipa_) { -+ LOG(SoftwareIsp, Error) -+ << "Creating IPA for software ISP failed"; -+ debayer_.reset(); -+ return; -+ } -+ -+ int ret = ipa_->init(IPASettings{ "No cfg file", "No sensor model" }, -+ debayer_->getStatsFD(), -+ sharedParams_.fd(), -+ sensorControls); -+ if (ret) { -+ LOG(SoftwareIsp, Error) << "IPA init failed"; -+ debayer_.reset(); -+ return; -+ } -+ -+ ipa_->setIspParams.connect(this, &SwIspSimple::saveIspParams); -+ -+ debayer_->moveToThread(&ispWorkerThread_); -+} -+ -+void SwIspSimple::processStats(const ControlList &sensorControls) -+{ -+ ASSERT(ipa_); -+ ipa_->processStats(sensorControls); -+} -+ -+Signal &SwIspSimple::getSignalSetSensorControls() -+{ -+ ASSERT(ipa_); -+ return ipa_->setSensorControls; -+} -+ -+bool SwIspSimple::isValid() const -+{ -+ return !!debayer_; -+} -+ -+std::vector SwIspSimple::formats(PixelFormat inputFormat) -+{ -+ ASSERT(debayer_ != nullptr); -+ -+ return debayer_->formats(inputFormat); -+} -+ -+SizeRange SwIspSimple::sizes(PixelFormat inputFormat, const Size &inputSize) -+{ -+ ASSERT(debayer_ != nullptr); -+ -+ return debayer_->sizes(inputFormat, inputSize); -+} -+ -+std::tuple -+SwIspSimple::strideAndFrameSize(const PixelFormat &outputFormat, const Size &size) -+{ -+ ASSERT(debayer_ != nullptr); -+ -+ return debayer_->strideAndFrameSize(outputFormat, size); -+} -+ -+int SwIspSimple::configure(const StreamConfiguration &inputCfg, -+ const std::vector> &outputCfgs, -+ const ControlInfoMap &sensorControls) -+{ -+ ASSERT(ipa_ != nullptr && debayer_ != nullptr); -+ -+ int ret = ipa_->configure(sensorControls); -+ if (ret < 0) -+ return ret; -+ -+ return debayer_->configure(inputCfg, outputCfgs); -+} -+ -+int SwIspSimple::exportBuffers(unsigned int output, unsigned int count, -+ std::vector> *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 planes{ outPlane }; -+ buffers->emplace_back(std::make_unique(std::move(planes))); -+ } -+ -+ return count; -+} -+ -+int SwIspSimple::queueBuffers(FrameBuffer *input, -+ const std::map &outputs) -+{ -+ unsigned int mask = 0; -+ -+ /* -+ * Validate the outputs as a sanity check: at least one output is -+ * required, all outputs must reference a valid stream and no two -+ * outputs can reference the same stream. -+ */ -+ if (outputs.empty()) -+ return -EINVAL; -+ -+ for (auto [index, buffer] : outputs) { -+ if (!buffer) -+ return -EINVAL; -+ if (index >= 1) /* only single stream atm */ -+ return -EINVAL; -+ if (mask & (1 << index)) -+ return -EINVAL; -+ -+ mask |= 1 << index; -+ } -+ -+ process(input, outputs.at(0)); -+ -+ return 0; -+} -+ -+int SwIspSimple::start() -+{ -+ int ret = ipa_->start(); -+ if (ret) -+ return ret; -+ -+ ispWorkerThread_.start(); -+ return 0; -+} -+ -+void SwIspSimple::stop() -+{ -+ ispWorkerThread_.exit(); -+ ispWorkerThread_.wait(); -+ -+ ipa_->stop(); -+} -+ -+void SwIspSimple::process(FrameBuffer *input, FrameBuffer *output) -+{ -+ debayer_->invokeMethod(&DebayerCpu::process, -+ ConnectionTypeQueued, input, output, debayerParams_); -+} -+ -+void SwIspSimple::saveIspParams([[maybe_unused]] int dummy) -+{ -+ debayerParams_ = *sharedParams_; -+} -+ -+void SwIspSimple::statsReady(int dummy) -+{ -+ ispStatsReady.emit(dummy); -+} -+ -+void SwIspSimple::inputReady(FrameBuffer *input) -+{ -+ inputBufferReady.emit(input); -+} -+ -+void SwIspSimple::outputReady(FrameBuffer *output) -+{ -+ outputBufferReady.emit(output); -+} -+ -+REGISTER_SOFTWAREISP(SwIspSimple) -+ -+} /* namespace libcamera */ --- -2.43.0 - diff --git a/modules/nixos/ipu6-softisp/libcamera/0014-libcamera-pipeline-simple-rename-converterBuffers_-a.patch b/modules/nixos/ipu6-softisp/libcamera/0014-libcamera-pipeline-simple-rename-converterBuffers_-a.patch deleted file mode 100644 index fc603fc..0000000 --- a/modules/nixos/ipu6-softisp/libcamera/0014-libcamera-pipeline-simple-rename-converterBuffers_-a.patch +++ /dev/null @@ -1,238 +0,0 @@ -From 155065b3d78c14173fd71c21fe0639f94c3da109 Mon Sep 17 00:00:00 2001 -From: Andrey Konovalov -Date: Fri, 1 Dec 2023 15:45:14 +0300 -Subject: [PATCH 14/25] libcamera: pipeline: simple: rename converterBuffers_ - and related vars - -The converterBuffers_ and the converterQueue_ are not that specific -to the Converter, and could be used by another entity doing the format -conversion. - -Rename converterBuffers_, converterQueue_, and useConverter_ to -conversionBuffers_, conversionQueue_ and useConversion_ to -disassociate them from the Converter. - -Signed-off-by: Andrey Konovalov -Signed-off-by: Hans de Goede -Tested-by: Bryan O'Donoghue # sc8280xp Lenovo x13s -Tested-by: Pavel Machek ---- - src/libcamera/pipeline/simple/simple.cpp | 63 ++++++++++++------------ - 1 file changed, 32 insertions(+), 31 deletions(-) - -diff --git a/src/libcamera/pipeline/simple/simple.cpp b/src/libcamera/pipeline/simple/simple.cpp -index 4d0e7255..fa298746 100644 ---- a/src/libcamera/pipeline/simple/simple.cpp -+++ b/src/libcamera/pipeline/simple/simple.cpp -@@ -269,17 +269,18 @@ public: - std::vector configs_; - std::map> formats_; - -+ std::vector> conversionBuffers_; -+ std::queue> conversionQueue_; -+ bool useConversion_; -+ - std::unique_ptr converter_; -- std::vector> converterBuffers_; -- bool useConverter_; -- std::queue> converterQueue_; - - private: - void tryPipeline(unsigned int code, const Size &size); - static std::vector 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 &outputs = -- converterQueue_.front(); -+ conversionQueue_.front(); - if (!outputs.empty()) { - FrameBuffer *outputBuffer = outputs.begin()->second; - if (outputBuffer) -@@ -801,14 +802,14 @@ void SimpleCameraData::bufferReady(FrameBuffer *buffer) - * conversion is needed. If there's no queued request, just requeue the - * captured buffer for capture. - */ -- if (useConverter_) { -- if (converterQueue_.empty()) { -+ if (useConversion_) { -+ if (conversionQueue_.empty()) { - video_->queueBuffer(buffer); - return; - } - -- converter_->queueBuffers(buffer, converterQueue_.front()); -- converterQueue_.pop(); -+ converter_->queueBuffers(buffer, conversionQueue_.front()); -+ conversionQueue_.pop(); - return; - } - -@@ -817,13 +818,13 @@ void SimpleCameraData::bufferReady(FrameBuffer *buffer) - pipe->completeRequest(request); - } - --void SimpleCameraData::converterInputDone(FrameBuffer *buffer) -+void SimpleCameraData::conversionInputDone(FrameBuffer *buffer) - { - /* Queue the input buffer back for capture. */ - video_->queueBuffer(buffer); - } - --void SimpleCameraData::converterOutputDone(FrameBuffer *buffer) -+void SimpleCameraData::conversionOutputDone(FrameBuffer *buffer) - { - SimplePipelineHandler *pipe = SimpleCameraData::pipe(); - -@@ -1159,14 +1160,14 @@ int SimplePipelineHandler::configure(Camera *camera, CameraConfiguration *c) - - /* Configure the converter if needed. */ - std::vector> outputCfgs; -- data->useConverter_ = config->needConversion(); -+ data->useConversion_ = config->needConversion(); - - for (unsigned int i = 0; i < config->size(); ++i) { - StreamConfiguration &cfg = config->at(i); - - cfg.setStream(&data->streams_[i]); - -- if (data->useConverter_) -+ if (data->useConversion_) - outputCfgs.push_back(cfg); - } - -@@ -1192,7 +1193,7 @@ int SimplePipelineHandler::exportFrameBuffers(Camera *camera, Stream *stream, - * Export buffers on the converter or capture video node, depending on - * whether the converter is used or not. - */ -- if (data->useConverter_) -+ if (data->useConversion_) - return data->converter_->exportBuffers(data->streamIndex(stream), - count, buffers); - else -@@ -1213,13 +1214,13 @@ int SimplePipelineHandler::start(Camera *camera, [[maybe_unused]] const ControlL - return -EBUSY; - } - -- if (data->useConverter_) { -+ if (data->useConversion_) { - /* - * When using the converter allocate a fixed number of internal - * buffers. - */ - ret = video->allocateBuffers(kNumInternalBuffers, -- &data->converterBuffers_); -+ &data->conversionBuffers_); - } else { - /* Otherwise, prepare for using buffers from the only stream. */ - Stream *stream = &data->streams_[0]; -@@ -1238,7 +1239,7 @@ int SimplePipelineHandler::start(Camera *camera, [[maybe_unused]] const ControlL - return ret; - } - -- if (data->useConverter_) { -+ if (data->useConversion_) { - ret = data->converter_->start(); - if (ret < 0) { - stop(camera); -@@ -1246,7 +1247,7 @@ int SimplePipelineHandler::start(Camera *camera, [[maybe_unused]] const ControlL - } - - /* Queue all internal buffers for capture. */ -- for (std::unique_ptr &buffer : data->converterBuffers_) -+ for (std::unique_ptr &buffer : data->conversionBuffers_) - video->queueBuffer(buffer.get()); - } - -@@ -1258,7 +1259,7 @@ void SimplePipelineHandler::stopDevice(Camera *camera) - SimpleCameraData *data = cameraData(camera); - V4L2VideoDevice *video = data->video_; - -- if (data->useConverter_) -+ if (data->useConversion_) - data->converter_->stop(); - - video->streamOff(); -@@ -1266,7 +1267,7 @@ void SimplePipelineHandler::stopDevice(Camera *camera) - - video->bufferReady.disconnect(data, &SimpleCameraData::bufferReady); - -- data->converterBuffers_.clear(); -+ data->conversionBuffers_.clear(); - - releasePipeline(data); - } -@@ -1284,7 +1285,7 @@ int SimplePipelineHandler::queueRequestDevice(Camera *camera, Request *request) - * queue, it will be handed to the converter in the capture - * completion handler. - */ -- if (data->useConverter_) { -+ if (data->useConversion_) { - buffers.emplace(data->streamIndex(stream), buffer); - } else { - ret = data->video_->queueBuffer(buffer); -@@ -1293,8 +1294,8 @@ int SimplePipelineHandler::queueRequestDevice(Camera *camera, Request *request) - } - } - -- if (data->useConverter_) -- data->converterQueue_.push(std::move(buffers)); -+ if (data->useConversion_) -+ data->conversionQueue_.push(std::move(buffers)); - - return 0; - } --- -2.43.0 - diff --git a/modules/nixos/ipu6-softisp/libcamera/0015-libcamera-pipeline-simple-enable-use-of-Soft-ISP-and.patch b/modules/nixos/ipu6-softisp/libcamera/0015-libcamera-pipeline-simple-enable-use-of-Soft-ISP-and.patch deleted file mode 100644 index f639a3b..0000000 --- a/modules/nixos/ipu6-softisp/libcamera/0015-libcamera-pipeline-simple-enable-use-of-Soft-ISP-and.patch +++ /dev/null @@ -1,243 +0,0 @@ -From ad61052d9aea1f1af6aaef9ce7e5d447c595187c Mon Sep 17 00:00:00 2001 -From: Andrey Konovalov -Date: Tue, 12 Dec 2023 02:02:37 +0300 -Subject: [PATCH 15/25] libcamera: pipeline: simple: enable use of Soft ISP and - Soft IPA - -To enable the Simple Soft ISP and Soft IPA for simple pipeline handler -configure the build with: - -Dpipelines=simple/simple -Dipas=simple/simple - -If the pipeline uses Converter, Soft ISP and Soft IPA aren't -available. - -Configuring the build with just: - -Dpipelines=simple -makes it to work like before - Soft ISP and Soft IPA aren't used. - -Signed-off-by: Andrey Konovalov -Signed-off-by: Hans de Goede -Tested-by: Bryan O'Donoghue # sc8280xp Lenovo x13s -Tested-by: Pavel Machek ---- - src/libcamera/pipeline/simple/simple.cpp | 111 ++++++++++++++++++----- - 1 file changed, 89 insertions(+), 22 deletions(-) - -diff --git a/src/libcamera/pipeline/simple/simple.cpp b/src/libcamera/pipeline/simple/simple.cpp -index fa298746..c76510c2 100644 ---- a/src/libcamera/pipeline/simple/simple.cpp -+++ b/src/libcamera/pipeline/simple/simple.cpp -@@ -34,6 +34,7 @@ - #include "libcamera/internal/device_enumerator.h" - #include "libcamera/internal/media_device.h" - #include "libcamera/internal/pipeline_handler.h" -+#include "libcamera/internal/software_isp.h" - #include "libcamera/internal/v4l2_subdevice.h" - #include "libcamera/internal/v4l2_videodevice.h" - -@@ -274,6 +275,7 @@ public: - bool useConversion_; - - std::unique_ptr converter_; -+ std::unique_ptr swIsp_; - - private: - void tryPipeline(unsigned int code, const Size &size); -@@ -281,6 +283,9 @@ private: - - void conversionInputDone(FrameBuffer *buffer); - void conversionOutputDone(FrameBuffer *buffer); -+ -+ void ispStatsReady(int dummy); -+ void setSensorControls(const ControlList &sensorControls); - }; - - class SimpleCameraConfiguration : public CameraConfiguration -@@ -509,6 +514,25 @@ int SimpleCameraData::init() - } - } - -+ /* -+ * Create SoftwareIsp unconditionally if no converter is used -+ * - to be revisited -+ */ -+ if (!converter_) { -+ swIsp_ = SoftwareIspFactoryBase::create(pipe, sensor_->controls()); -+ if (!swIsp_) { -+ LOG(SimplePipeline, Warning) -+ << "Failed to create software ISP, disabling software debayering"; -+ swIsp_.reset(); -+ } else { -+ swIsp_->inputBufferReady.connect(this, &SimpleCameraData::conversionInputDone); -+ swIsp_->outputBufferReady.connect(this, &SimpleCameraData::conversionOutputDone); -+ swIsp_->ispStatsReady.connect(this, &SimpleCameraData::ispStatsReady); -+ -+ swIsp_->getSignalSetSensorControls().connect(this, &SimpleCameraData::setSensorControls); -+ } -+ } -+ - video_ = pipe->video(entities_.back().entity); - ASSERT(video_); - -@@ -599,12 +623,20 @@ void SimpleCameraData::tryPipeline(unsigned int code, const Size &size) - config.captureFormat = pixelFormat; - config.captureSize = format.size; - -- if (!converter_) { -- config.outputFormats = { pixelFormat }; -- config.outputSizes = config.captureSize; -- } else { -+ if (converter_) { - config.outputFormats = converter_->formats(pixelFormat); - config.outputSizes = converter_->sizes(format.size); -+ } else if (swIsp_) { -+ config.outputFormats = swIsp_->formats(pixelFormat); -+ config.outputSizes = swIsp_->sizes(pixelFormat, format.size); -+ if (config.outputFormats.empty()) { -+ /* Do not use swIsp for unsupported pixelFormat's */ -+ config.outputFormats = { pixelFormat }; -+ config.outputSizes = config.captureSize; -+ } -+ } else { -+ config.outputFormats = { pixelFormat }; -+ config.outputSizes = config.captureSize; - } - - configs_.push_back(config); -@@ -750,9 +782,9 @@ void SimpleCameraData::bufferReady(FrameBuffer *buffer) - } - - /* -- * The converter is in use. Requeue the internal buffer for -- * capture (unless the stream is being stopped), and complete -- * the request with all the user-facing buffers. -+ * The converter or Software ISP is in use. Requeue the internal -+ * buffer for capture (unless the stream is being stopped), and -+ * complete the request with all the user-facing buffers. - */ - if (buffer->metadata().status != FrameMetadata::FrameCancelled) - video_->queueBuffer(buffer); -@@ -798,9 +830,9 @@ void SimpleCameraData::bufferReady(FrameBuffer *buffer) - buffer->metadata().timestamp); - - /* -- * Queue the captured and the request buffer to the converter if format -- * conversion is needed. If there's no queued request, just requeue the -- * captured buffer for capture. -+ * Queue the captured and the request buffer to the converter or Software -+ * ISP if format conversion is needed. If there's no queued request, just -+ * requeue the captured buffer for capture. - */ - if (useConversion_) { - if (conversionQueue_.empty()) { -@@ -808,7 +840,11 @@ void SimpleCameraData::bufferReady(FrameBuffer *buffer) - return; - } - -- converter_->queueBuffers(buffer, conversionQueue_.front()); -+ if (converter_) -+ converter_->queueBuffers(buffer, conversionQueue_.front()); -+ else -+ swIsp_->queueBuffers(buffer, conversionQueue_.front()); -+ - conversionQueue_.pop(); - return; - } -@@ -834,6 +870,18 @@ void SimpleCameraData::conversionOutputDone(FrameBuffer *buffer) - pipe->completeRequest(request); - } - -+void SimpleCameraData::ispStatsReady([[maybe_unused]] int dummy) -+{ -+ swIsp_->processStats(sensor_->getControls({ V4L2_CID_ANALOGUE_GAIN, -+ V4L2_CID_EXPOSURE })); -+} -+ -+void SimpleCameraData::setSensorControls(const ControlList &sensorControls) -+{ -+ ControlList ctrls(sensorControls); -+ sensor_->setControls(&ctrls); -+} -+ - /* Retrieve all source pads connected to a sink pad through active routes. */ - std::vector SimpleCameraData::routedSourcePads(MediaPad *sink) - { -@@ -1016,8 +1064,10 @@ CameraConfiguration::Status SimpleCameraConfiguration::validate() - /* Set the stride, frameSize and bufferCount. */ - if (needConversion_) { - std::tie(cfg.stride, cfg.frameSize) = -- data_->converter_->strideAndFrameSize(cfg.pixelFormat, -- cfg.size); -+ (data_->converter_) ? data_->converter_->strideAndFrameSize(cfg.pixelFormat, -+ cfg.size) -+ : data_->swIsp_->strideAndFrameSize(cfg.pixelFormat, -+ cfg.size); - if (cfg.stride == 0) - return Invalid; - } else { -@@ -1180,7 +1230,9 @@ int SimplePipelineHandler::configure(Camera *camera, CameraConfiguration *c) - inputCfg.stride = captureFormat.planes[0].bpl; - inputCfg.bufferCount = kNumInternalBuffers; - -- return data->converter_->configure(inputCfg, outputCfgs); -+ return (data->converter_) ? data->converter_->configure(inputCfg, outputCfgs) -+ : data->swIsp_->configure(inputCfg, outputCfgs, -+ data->sensor_->controls()); - } - - int SimplePipelineHandler::exportFrameBuffers(Camera *camera, Stream *stream, -@@ -1194,8 +1246,10 @@ int SimplePipelineHandler::exportFrameBuffers(Camera *camera, Stream *stream, - * whether the converter is used or not. - */ - if (data->useConversion_) -- return data->converter_->exportBuffers(data->streamIndex(stream), -- count, buffers); -+ return (data->converter_) ? data->converter_->exportBuffers(data->streamIndex(stream), -+ count, buffers) -+ : data->swIsp_->exportBuffers(data->streamIndex(stream), -+ count, buffers); - else - return data->video_->exportBuffers(count, buffers); - } -@@ -1240,10 +1294,18 @@ int SimplePipelineHandler::start(Camera *camera, [[maybe_unused]] const ControlL - } - - if (data->useConversion_) { -- ret = data->converter_->start(); -- if (ret < 0) { -- stop(camera); -- return ret; -+ if (data->converter_) { -+ ret = data->converter_->start(); -+ if (ret < 0) { -+ stop(camera); -+ return ret; -+ } -+ } else if (data->swIsp_) { -+ ret = data->swIsp_->start(); -+ if (ret < 0) { -+ stop(camera); -+ return ret; -+ } - } - - /* Queue all internal buffers for capture. */ -@@ -1259,8 +1321,13 @@ void SimplePipelineHandler::stopDevice(Camera *camera) - SimpleCameraData *data = cameraData(camera); - V4L2VideoDevice *video = data->video_; - -- if (data->useConversion_) -- data->converter_->stop(); -+ if (data->useConversion_) { -+ if (data->converter_) -+ data->converter_->stop(); -+ else if (data->swIsp_) { -+ data->swIsp_->stop(); -+ } -+ } - - video->streamOff(); - video->releaseBuffers(); --- -2.43.0 - diff --git a/modules/nixos/ipu6-softisp/libcamera/0016-libcamera-swstats_cpu-Add-support-for-8-10-and-12-bp.patch b/modules/nixos/ipu6-softisp/libcamera/0016-libcamera-swstats_cpu-Add-support-for-8-10-and-12-bp.patch deleted file mode 100644 index 0438885..0000000 --- a/modules/nixos/ipu6-softisp/libcamera/0016-libcamera-swstats_cpu-Add-support-for-8-10-and-12-bp.patch +++ /dev/null @@ -1,199 +0,0 @@ -From 66ef9f32e67a96655d10ba38f7a830b3bbfe50f2 Mon Sep 17 00:00:00 2001 -From: Hans de Goede -Date: Thu, 14 Dec 2023 19:09:44 +0100 -Subject: [PATCH 16/25] libcamera: swstats_cpu: Add support for 8, 10 and 12 - bpp unpacked bayer input - -Add support for 8, 10 and 12 bpp unpacked bayer input for all 4 standard -bayer orders. - -Tested-by: Bryan O'Donoghue # sc8280xp Lenovo x13s -Tested-by: Pavel Machek -Signed-off-by: Kieran Bingham -Signed-off-by: Hans de Goede ---- - .../internal/software_isp/swstats_cpu.h | 9 ++ - src/libcamera/software_isp/swstats_cpu.cpp | 126 ++++++++++++++++++ - 2 files changed, 135 insertions(+) - -diff --git a/include/libcamera/internal/software_isp/swstats_cpu.h b/include/libcamera/internal/software_isp/swstats_cpu.h -index 8bb86e98..e7abc6bb 100644 ---- a/include/libcamera/internal/software_isp/swstats_cpu.h -+++ b/include/libcamera/internal/software_isp/swstats_cpu.h -@@ -11,6 +11,7 @@ - - #pragma once - -+#include "libcamera/internal/bayer_format.h" - #include "libcamera/internal/shared_mem_object.h" - #include "libcamera/internal/software_isp/swisp_stats.h" - #include "libcamera/internal/software_isp/swstats.h" -@@ -31,6 +32,14 @@ public: - const SharedFD &getStatsFD() { return sharedStats_.fd(); } - int configure(const StreamConfiguration &inputCfg); - private: -+ int setupStandardBayerOrder(BayerFormat::Order order); -+ /* Bayer 8 bpp unpacked */ -+ void statsBGGR8Line0(const uint8_t *src[]); -+ /* Bayer 10 bpp unpacked */ -+ void statsBGGR10Line0(const uint8_t *src[]); -+ /* Bayer 12 bpp unpacked */ -+ void statsBGGR12Line0(const uint8_t *src[]); -+ /* Bayer 10 bpp packed */ - void statsBGGR10PLine0(const uint8_t *src[]); - void statsGBRG10PLine0(const uint8_t *src[]); - void resetStats(void); -diff --git a/src/libcamera/software_isp/swstats_cpu.cpp b/src/libcamera/software_isp/swstats_cpu.cpp -index 59453d07..87550371 100644 ---- a/src/libcamera/software_isp/swstats_cpu.cpp -+++ b/src/libcamera/software_isp/swstats_cpu.cpp -@@ -59,6 +59,83 @@ static const unsigned int BLUE_Y_MUL = 29; /* 0.11 * 256 */ - stats_.sumG_ += sumG; \ - stats_.sumB_ += sumB; - -+void SwStatsCpu::statsBGGR8Line0(const uint8_t *src[]) -+{ -+ const uint8_t *src0 = src[1] + window_.x; -+ const uint8_t *src1 = src[2] + window_.x; -+ -+ SWISP_LINARO_START_LINE_STATS(uint8_t) -+ -+ if (swap_lines_) -+ std::swap(src0, src1); -+ -+ /* x += 4 sample every other 2x2 block */ -+ for (int x = 0; x < (int)window_.width; x += 4) { -+ b = src0[x]; -+ g = src0[x + 1]; -+ g2 = src1[x]; -+ r = src1[x + 1]; -+ -+ g = (g + g2) / 2; -+ -+ SWISP_LINARO_ACCUMULATE_LINE_STATS(1) -+ } -+ -+ SWISP_LINARO_FINISH_LINE_STATS() -+} -+ -+void SwStatsCpu::statsBGGR10Line0(const uint8_t *src[]) -+{ -+ const uint16_t *src0 = (const uint16_t *)src[1] + window_.x; -+ const uint16_t *src1 = (const uint16_t *)src[2] + window_.x; -+ -+ SWISP_LINARO_START_LINE_STATS(uint16_t) -+ -+ if (swap_lines_) -+ std::swap(src0, src1); -+ -+ /* x += 4 sample every other 2x2 block */ -+ for (int x = 0; x < (int)window_.width; x += 4) { -+ b = src0[x]; -+ g = src0[x + 1]; -+ g2 = src1[x]; -+ r = src1[x + 1]; -+ -+ g = (g + g2) / 2; -+ -+ /* divide Y by 4 for 10 -> 8 bpp value */ -+ SWISP_LINARO_ACCUMULATE_LINE_STATS(4) -+ } -+ -+ SWISP_LINARO_FINISH_LINE_STATS() -+} -+ -+void SwStatsCpu::statsBGGR12Line0(const uint8_t *src[]) -+{ -+ const uint16_t *src0 = (const uint16_t *)src[1] + window_.x; -+ const uint16_t *src1 = (const uint16_t *)src[2] + window_.x; -+ -+ SWISP_LINARO_START_LINE_STATS(uint16_t) -+ -+ if (swap_lines_) -+ std::swap(src0, src1); -+ -+ /* x += 4 sample every other 2x2 block */ -+ for (int x = 0; x < (int)window_.width; x += 4) { -+ b = src0[x]; -+ g = src0[x + 1]; -+ g2 = src1[x]; -+ r = src1[x + 1]; -+ -+ g = (g + g2) / 2; -+ -+ /* divide Y by 16 for 12 -> 8 bpp value */ -+ SWISP_LINARO_ACCUMULATE_LINE_STATS(16) -+ } -+ -+ SWISP_LINARO_FINISH_LINE_STATS() -+} -+ - static inline __attribute__((always_inline)) void - statsBayer10P(const int width, const uint8_t *src0, const uint8_t *src1, bool bggr, SwIspStats &stats_) - { -@@ -124,6 +201,39 @@ void SwStatsCpu::finishStats(void) - statsReady.emit(0); - } - -+/* -+ * Check if order is a standard Bayer order and setup x_shift_ and swap_lines_ -+ * so that a single BGGR stats function can be used for all 4 standard orders. -+ */ -+int SwStatsCpu::setupStandardBayerOrder(BayerFormat::Order order) -+{ -+ switch (order) { -+ case BayerFormat::BGGR: -+ x_shift_ = 0; -+ swap_lines_ = false; -+ break; -+ case BayerFormat::GBRG: -+ x_shift_ = 1; /* BGGR -> GBRG */ -+ swap_lines_ = false; -+ break; -+ case BayerFormat::GRBG: -+ x_shift_ = 0; -+ swap_lines_ = true; /* BGGR -> GRBG */ -+ break; -+ case BayerFormat::RGGB: -+ x_shift_ = 1; /* BGGR -> GBRG */ -+ swap_lines_ = true; /* GBRG -> RGGB */ -+ break; -+ default: -+ return -EINVAL; -+ } -+ -+ patternSize_.height = 2; -+ patternSize_.width = 2; -+ y_skip_mask_ = 0x02; /* Skip every 3th and 4th line */ -+ return 0; -+} -+ - int SwStatsCpu::configure(const StreamConfiguration &inputCfg) - { - BayerFormat bayerFormat = -@@ -132,6 +242,22 @@ int SwStatsCpu::configure(const StreamConfiguration &inputCfg) - startFrame_ = (SwStats::statsVoidFn)&SwStatsCpu::resetStats; - finishFrame_ = (SwStats::statsVoidFn)&SwStatsCpu::finishStats; - -+ if (bayerFormat.packing == BayerFormat::Packing::None && -+ setupStandardBayerOrder(bayerFormat.order) == 0) { -+ bpp_ = (bayerFormat.bitDepth + 7) & ~7; -+ switch (bayerFormat.bitDepth) { -+ case 8: -+ stats0_ = (SwStats::statsProcessFn)&SwStatsCpu::statsBGGR8Line0; -+ return 0; -+ case 10: -+ stats0_ = (SwStats::statsProcessFn)&SwStatsCpu::statsBGGR10Line0; -+ return 0; -+ case 12: -+ stats0_ = (SwStats::statsProcessFn)&SwStatsCpu::statsBGGR12Line0; -+ return 0; -+ } -+ } -+ - if (bayerFormat.bitDepth == 10 && - bayerFormat.packing == BayerFormat::Packing::CSI2) { - bpp_ = 10; --- -2.43.0 - diff --git a/modules/nixos/ipu6-softisp/libcamera/0017-libcamera-debayer_cpu-Add-support-for-8-10-and-12-bp.patch b/modules/nixos/ipu6-softisp/libcamera/0017-libcamera-debayer_cpu-Add-support-for-8-10-and-12-bp.patch deleted file mode 100644 index a5a992f..0000000 --- a/modules/nixos/ipu6-softisp/libcamera/0017-libcamera-debayer_cpu-Add-support-for-8-10-and-12-bp.patch +++ /dev/null @@ -1,237 +0,0 @@ -From b7b211eb56d98d5b170bd73a23b55aeb45bde8c5 Mon Sep 17 00:00:00 2001 -From: Hans de Goede -Date: Thu, 14 Dec 2023 19:57:15 +0100 -Subject: [PATCH 17/25] libcamera: debayer_cpu: Add support for 8, 10 and 12 - bpp unpacked bayer input - -Add support for 8, 10 and 12 bpp unpacked bayer input for all 4 standard -bayer orders. - -Tested-by: Bryan O'Donoghue # sc8280xp Lenovo x13s -Tested-by: Pavel Machek -Signed-off-by: Kieran Bingham -Signed-off-by: Hans de Goede ---- - .../internal/software_isp/debayer_cpu.h | 13 ++ - src/libcamera/software_isp/debayer_cpu.cpp | 128 ++++++++++++++++++ - 2 files changed, 141 insertions(+) - -diff --git a/include/libcamera/internal/software_isp/debayer_cpu.h b/include/libcamera/internal/software_isp/debayer_cpu.h -index 78573f44..1147b368 100644 ---- a/include/libcamera/internal/software_isp/debayer_cpu.h -+++ b/include/libcamera/internal/software_isp/debayer_cpu.h -@@ -17,6 +17,7 @@ - - #include - -+#include "libcamera/internal/bayer_format.h" - #include "libcamera/internal/software_isp/swstats_cpu.h" - #include "libcamera/internal/software_isp/debayer.h" - -@@ -75,11 +76,21 @@ public: - * \return The output frame size. - */ - unsigned int frameSize() { return outputConfig_.frameSize; } -+ - private: - void initLinePointers(const uint8_t *linePointers[], const uint8_t *src); - void shiftLinePointers(const uint8_t *linePointers[], const uint8_t *src); - void process2(const uint8_t *src, uint8_t *dst); - void process4(const uint8_t *src, uint8_t *dst); -+ /* 8-bit raw bayer format */ -+ void debayer8_BGBG_BGR888(uint8_t *dst, const uint8_t *src[]); -+ void debayer8_GRGR_BGR888(uint8_t *dst, const uint8_t *src[]); -+ /* unpacked 10-bit raw bayer format */ -+ void debayer10_BGBG_BGR888(uint8_t *dst, const uint8_t *src[]); -+ void debayer10_GRGR_BGR888(uint8_t *dst, const uint8_t *src[]); -+ /* unpacked 12-bit raw bayer format */ -+ void debayer12_BGBG_BGR888(uint8_t *dst, const uint8_t *src[]); -+ void debayer12_GRGR_BGR888(uint8_t *dst, const uint8_t *src[]); - /* CSI-2 packed 10-bit raw bayer format (all the 4 orders) */ - void debayer10P_BGBG_BGR888(uint8_t *dst, const uint8_t *src[]); - void debayer10P_GRGR_BGR888(uint8_t *dst, const uint8_t *src[]); -@@ -103,6 +114,7 @@ private: - - int getInputConfig(PixelFormat inputFormat, DebayerInputConfig &config); - int getOutputConfig(PixelFormat outputFormat, DebayerOutputConfig &config); -+ int setupStandardBayerOrder(BayerFormat::Order order); - int setDebayerFunctions(PixelFormat inputFormat, PixelFormat outputFormat); - - uint8_t gamma_[1024]; -@@ -119,6 +131,7 @@ private: - std::unique_ptr stats_; - uint8_t *lineBuffers_[5]; - unsigned int lineBufferIndex_; -+ unsigned int x_shift_; /* Offset of 0/1 applied to window_.x */ - bool enableInputMemcpy_; - float gamma_correction_; - int measuredFrames_; -diff --git a/src/libcamera/software_isp/debayer_cpu.cpp b/src/libcamera/software_isp/debayer_cpu.cpp -index e0c3c658..7b7623b7 100644 ---- a/src/libcamera/software_isp/debayer_cpu.cpp -+++ b/src/libcamera/software_isp/debayer_cpu.cpp -@@ -45,6 +45,11 @@ DebayerCpu::~DebayerCpu() - free(lineBuffers_[i]); - } - -+#define DECLARE_SRC_POINTERS(pixel_t) \ -+ const pixel_t *prev = (const pixel_t *)src[0] + x_shift_; \ -+ const pixel_t *curr = (const pixel_t *)src[1] + x_shift_; \ -+ const pixel_t *next = (const pixel_t *)src[2] + x_shift_; -+ - // RGR - // GBG - // RGR -@@ -81,6 +86,70 @@ DebayerCpu::~DebayerCpu() - *dst++ = red_[curr[x] / (div)]; \ - x++; - -+void DebayerCpu::debayer8_BGBG_BGR888(uint8_t *dst, const uint8_t *src[]) -+{ -+ DECLARE_SRC_POINTERS(uint8_t) -+ -+ for (int x = 0; x < (int)window_.width;) { -+ BGGR_BGR888(1, 1, 1) -+ GBRG_BGR888(1, 1, 1) -+ } -+} -+ -+void DebayerCpu::debayer8_GRGR_BGR888(uint8_t *dst, const uint8_t *src[]) -+{ -+ DECLARE_SRC_POINTERS(uint8_t) -+ -+ for (int x = 0; x < (int)window_.width;) { -+ GRBG_BGR888(1, 1, 1) -+ RGGB_BGR888(1, 1, 1) -+ } -+} -+ -+void DebayerCpu::debayer10_BGBG_BGR888(uint8_t *dst, const uint8_t *src[]) -+{ -+ DECLARE_SRC_POINTERS(uint16_t) -+ -+ for (int x = 0; x < (int)window_.width;) { -+ /* divide values by 4 for 10 -> 8 bpp value */ -+ BGGR_BGR888(1, 1, 4) -+ GBRG_BGR888(1, 1, 4) -+ } -+} -+ -+void DebayerCpu::debayer10_GRGR_BGR888(uint8_t *dst, const uint8_t *src[]) -+{ -+ DECLARE_SRC_POINTERS(uint16_t) -+ -+ for (int x = 0; x < (int)window_.width;) { -+ /* divide values by 4 for 10 -> 8 bpp value */ -+ GRBG_BGR888(1, 1, 4) -+ RGGB_BGR888(1, 1, 4) -+ } -+} -+ -+void DebayerCpu::debayer12_BGBG_BGR888(uint8_t *dst, const uint8_t *src[]) -+{ -+ DECLARE_SRC_POINTERS(uint16_t) -+ -+ for (int x = 0; x < (int)window_.width;) { -+ /* divide values by 16 for 12 -> 8 bpp value */ -+ BGGR_BGR888(1, 1, 16) -+ GBRG_BGR888(1, 1, 16) -+ } -+} -+ -+void DebayerCpu::debayer12_GRGR_BGR888(uint8_t *dst, const uint8_t *src[]) -+{ -+ DECLARE_SRC_POINTERS(uint16_t) -+ -+ for (int x = 0; x < (int)window_.width;) { -+ /* divide values by 16 for 12 -> 8 bpp value */ -+ GRBG_BGR888(1, 1, 16) -+ RGGB_BGR888(1, 1, 16) -+ } -+} -+ - void DebayerCpu::debayer10P_BGBG_BGR888(uint8_t *dst, const uint8_t *src[]) - { - const int width_in_bytes = window_.width * 5 / 4; -@@ -170,6 +239,16 @@ int DebayerCpu::getInputConfig(PixelFormat inputFormat, DebayerInputConfig &conf - BayerFormat bayerFormat = - BayerFormat::fromPixelFormat(inputFormat); - -+ if ((bayerFormat.bitDepth == 8 || bayerFormat.bitDepth == 10 || bayerFormat.bitDepth == 12) && -+ bayerFormat.packing == BayerFormat::Packing::None && -+ isStandardBayerOrder(bayerFormat.order)) { -+ config.bpp = (bayerFormat.bitDepth + 7) & ~7; -+ config.patternSize.width = 2; -+ config.patternSize.height = 2; -+ config.outputFormats = std::vector({ formats::RGB888 }); -+ return 0; -+ } -+ - if (bayerFormat.bitDepth == 10 && - bayerFormat.packing == BayerFormat::Packing::CSI2 && - isStandardBayerOrder(bayerFormat.order)) { -@@ -197,12 +276,61 @@ int DebayerCpu::getOutputConfig(PixelFormat outputFormat, DebayerOutputConfig &c - return -EINVAL; - } - -+/* -+ * Check for standard Bayer orders and set x_shift_ and swap debayer0/1, so that -+ * a single pair of BGGR debayer functions can be used for all 4 standard orders. -+ */ -+int DebayerCpu::setupStandardBayerOrder(BayerFormat::Order order) -+{ -+ switch (order) { -+ case BayerFormat::BGGR: -+ break; -+ case BayerFormat::GBRG: -+ x_shift_ = 1; /* BGGR -> GBRG */ -+ break; -+ case BayerFormat::GRBG: -+ std::swap(debayer0_, debayer1_); /* BGGR -> GRBG */ -+ break; -+ case BayerFormat::RGGB: -+ x_shift_ = 1; /* BGGR -> GBRG */ -+ std::swap(debayer0_, debayer1_); /* GBRG -> RGGB */ -+ break; -+ default: -+ return -EINVAL; -+ } -+ -+ return 0; -+} -+ - /* TODO: this ignores outputFormat since there is only 1 supported outputFormat for now */ - int DebayerCpu::setDebayerFunctions(PixelFormat inputFormat, [[maybe_unused]] PixelFormat outputFormat) - { - BayerFormat bayerFormat = - BayerFormat::fromPixelFormat(inputFormat); - -+ x_shift_ = 0; -+ -+ if ((bayerFormat.bitDepth == 8 || bayerFormat.bitDepth == 10 || bayerFormat.bitDepth == 12) && -+ bayerFormat.packing == BayerFormat::Packing::None && -+ isStandardBayerOrder(bayerFormat.order)) { -+ switch (bayerFormat.bitDepth) { -+ case 8: -+ debayer0_ = &DebayerCpu::debayer8_BGBG_BGR888; -+ debayer1_ = &DebayerCpu::debayer8_GRGR_BGR888; -+ break; -+ case 10: -+ debayer0_ = &DebayerCpu::debayer10_BGBG_BGR888; -+ debayer1_ = &DebayerCpu::debayer10_GRGR_BGR888; -+ break; -+ case 12: -+ debayer0_ = &DebayerCpu::debayer12_BGBG_BGR888; -+ debayer1_ = &DebayerCpu::debayer12_GRGR_BGR888; -+ break; -+ } -+ setupStandardBayerOrder(bayerFormat.order); -+ return 0; -+ } -+ - if (bayerFormat.bitDepth == 10 && - bayerFormat.packing == BayerFormat::Packing::CSI2) { - switch (bayerFormat.order) { --- -2.43.0 - diff --git a/modules/nixos/ipu6-softisp/libcamera/0018-libcamera-debayer_cpu-Add-BGR888-output-support.patch b/modules/nixos/ipu6-softisp/libcamera/0018-libcamera-debayer_cpu-Add-BGR888-output-support.patch deleted file mode 100644 index 50d826b..0000000 --- a/modules/nixos/ipu6-softisp/libcamera/0018-libcamera-debayer_cpu-Add-BGR888-output-support.patch +++ /dev/null @@ -1,125 +0,0 @@ -From b835b2c90785ee02bc98888bf165713d16c24cc4 Mon Sep 17 00:00:00 2001 -From: Hans de Goede -Date: Mon, 18 Dec 2023 19:21:07 +0100 -Subject: [PATCH 18/25] libcamera: debayer_cpu: Add BGR888 output support - -BGR888 is RGB888 with the red and blue pixels swapped, adjust -the debayering to swap the red and blue pixels in the bayer pattern -to add support for writing formats::BGR888. - -Signed-off-by: Hans de Goede -Tested-by: Bryan O'Donoghue # sc8280xp Lenovo x13s -Tested-by: Pavel Machek ---- - .../internal/software_isp/debayer_cpu.h | 1 + - src/libcamera/software_isp/debayer_cpu.cpp | 43 ++++++++++++++++--- - 2 files changed, 39 insertions(+), 5 deletions(-) - -diff --git a/include/libcamera/internal/software_isp/debayer_cpu.h b/include/libcamera/internal/software_isp/debayer_cpu.h -index 1147b368..bdeab7c0 100644 ---- a/include/libcamera/internal/software_isp/debayer_cpu.h -+++ b/include/libcamera/internal/software_isp/debayer_cpu.h -@@ -133,6 +133,7 @@ private: - unsigned int lineBufferIndex_; - unsigned int x_shift_; /* Offset of 0/1 applied to window_.x */ - bool enableInputMemcpy_; -+ bool swapRedBlueGains_; - float gamma_correction_; - int measuredFrames_; - int64_t frameProcessTime_; -diff --git a/src/libcamera/software_isp/debayer_cpu.cpp b/src/libcamera/software_isp/debayer_cpu.cpp -index 7b7623b7..0edea4d3 100644 ---- a/src/libcamera/software_isp/debayer_cpu.cpp -+++ b/src/libcamera/software_isp/debayer_cpu.cpp -@@ -245,7 +245,7 @@ int DebayerCpu::getInputConfig(PixelFormat inputFormat, DebayerInputConfig &conf - config.bpp = (bayerFormat.bitDepth + 7) & ~7; - config.patternSize.width = 2; - config.patternSize.height = 2; -- config.outputFormats = std::vector({ formats::RGB888 }); -+ config.outputFormats = std::vector({ formats::RGB888, formats::BGR888 }); - return 0; - } - -@@ -255,7 +255,7 @@ int DebayerCpu::getInputConfig(PixelFormat inputFormat, DebayerInputConfig &conf - config.bpp = 10; - config.patternSize.width = 4; /* 5 bytes per *4* pixels */ - config.patternSize.height = 2; -- config.outputFormats = std::vector({ formats::RGB888 }); -+ config.outputFormats = std::vector({ formats::RGB888, formats::BGR888 }); - return 0; - } - -@@ -266,7 +266,7 @@ int DebayerCpu::getInputConfig(PixelFormat inputFormat, DebayerInputConfig &conf - - int DebayerCpu::getOutputConfig(PixelFormat outputFormat, DebayerOutputConfig &config) - { -- if (outputFormat == formats::RGB888) { -+ if (outputFormat == formats::RGB888 || outputFormat == formats::BGR888) { - config.bpp = 24; - return 0; - } -@@ -302,12 +302,41 @@ int DebayerCpu::setupStandardBayerOrder(BayerFormat::Order order) - return 0; - } - --/* TODO: this ignores outputFormat since there is only 1 supported outputFormat for now */ --int DebayerCpu::setDebayerFunctions(PixelFormat inputFormat, [[maybe_unused]] PixelFormat outputFormat) -+int DebayerCpu::setDebayerFunctions(PixelFormat inputFormat, PixelFormat outputFormat) - { - BayerFormat bayerFormat = - BayerFormat::fromPixelFormat(inputFormat); - -+ swapRedBlueGains_ = false; -+ -+ switch (outputFormat) { -+ case formats::RGB888: -+ break; -+ case formats::BGR888: -+ /* Swap R and B in bayer order to generate BGR888 instead of RGB888 */ -+ swapRedBlueGains_ = true; -+ -+ switch (bayerFormat.order) { -+ case BayerFormat::BGGR: -+ bayerFormat.order = BayerFormat::RGGB; -+ break; -+ case BayerFormat::GBRG: -+ bayerFormat.order = BayerFormat::GRBG; -+ break; -+ case BayerFormat::GRBG: -+ bayerFormat.order = BayerFormat::GBRG; -+ break; -+ case BayerFormat::RGGB: -+ bayerFormat.order = BayerFormat::BGGR; -+ break; -+ default: -+ goto invalid_fmt; -+ } -+ break; -+ default: -+ goto invalid_fmt; -+ } -+ - x_shift_ = 0; - - if ((bayerFormat.bitDepth == 8 || bayerFormat.bitDepth == 10 || bayerFormat.bitDepth == 12) && -@@ -355,6 +384,7 @@ int DebayerCpu::setDebayerFunctions(PixelFormat inputFormat, [[maybe_unused]] Pi - } - } - -+invalid_fmt: - LOG(Debayer, Error) << "Unsupported input output format combination"; - return -EINVAL; - } -@@ -594,6 +624,9 @@ void DebayerCpu::process(FrameBuffer *input, FrameBuffer *output, DebayerParams - gamma_correction_ = params.gamma; - } - -+ if (swapRedBlueGains_) -+ std::swap(params.gainR, params.gainB); -+ - for (int i = 0; i < 256; i++) { - int idx; - --- -2.43.0 - diff --git a/modules/nixos/ipu6-softisp/libcamera/0019-libcamera-pipeline-simple-Enable-simplepipeline-for-.patch b/modules/nixos/ipu6-softisp/libcamera/0019-libcamera-pipeline-simple-Enable-simplepipeline-for-.patch deleted file mode 100644 index cdbd0b9..0000000 --- a/modules/nixos/ipu6-softisp/libcamera/0019-libcamera-pipeline-simple-Enable-simplepipeline-for-.patch +++ /dev/null @@ -1,30 +0,0 @@ -From eb45bdfe66af7844a779bc6fcf923cd951336309 Mon Sep 17 00:00:00 2001 -From: Dennis Bonke -Date: Fri, 6 Oct 2023 10:39:45 +0200 -Subject: [PATCH 19/25] libcamera: pipeline: simple: Enable simplepipeline for - intel-ipu6 DNU - -Do Not Upstream, first the ipu6 CSI receiver code needs to land in -the kernel. - -Signed-off-by: Dennis Bonke -Signed-off-by: Hans de Goede ---- - src/libcamera/pipeline/simple/simple.cpp | 1 + - 1 file changed, 1 insertion(+) - -diff --git a/src/libcamera/pipeline/simple/simple.cpp b/src/libcamera/pipeline/simple/simple.cpp -index c76510c2..130843cd 100644 ---- a/src/libcamera/pipeline/simple/simple.cpp -+++ b/src/libcamera/pipeline/simple/simple.cpp -@@ -197,6 +197,7 @@ static const SimplePipelineInfo supportedDevices[] = { - { "mxc-isi", {} }, - { "qcom-camss", {} }, - { "sun6i-csi", {} }, -+ { "intel-ipu6", {} }, - }; - - } /* namespace */ --- -2.43.0 - diff --git a/modules/nixos/ipu6-softisp/libcamera/0020-libcamera-Add-support-for-IGIG_GBGR_IGIG_GRGB-bayer-.patch b/modules/nixos/ipu6-softisp/libcamera/0020-libcamera-Add-support-for-IGIG_GBGR_IGIG_GRGB-bayer-.patch deleted file mode 100644 index 768ec07..0000000 --- a/modules/nixos/ipu6-softisp/libcamera/0020-libcamera-Add-support-for-IGIG_GBGR_IGIG_GRGB-bayer-.patch +++ /dev/null @@ -1,237 +0,0 @@ -From e03beabbad83c4c283c7f1c2c4798b6c3e2eaf06 Mon Sep 17 00:00:00 2001 -From: Hans de Goede -Date: Tue, 19 Dec 2023 11:16:26 +0100 -Subject: [PATCH 20/25] libcamera: Add support for IGIG_GBGR_IGIG_GRGB bayer - order DNU - -The ov01a1s sensor has the following bayer pattern (4x4 tile repeating): - -IGIG -GBGR -IGIG -GRGB - -Add support for this PixelFormat to libcamera. - -Do Not Upstream, first the include/linux/media-bus-format.h and -include/linux/videodev2.h changes need to land in the upstream kernel. - -Signed-off-by: Hans de Goede ---- - 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 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 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 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 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 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 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 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 formatInfoMap = { - { MEDIA_BUS_FMT_SGBRG10_1X10, { 10, "SGBRG10_1X10", PixelFormatInfo::ColourEncodingRAW } }, - { MEDIA_BUS_FMT_SGRBG10_1X10, { 10, "SGRBG10_1X10", PixelFormatInfo::ColourEncodingRAW } }, - { MEDIA_BUS_FMT_SRGGB10_1X10, { 10, "SRGGB10_1X10", PixelFormatInfo::ColourEncodingRAW } }, -+ { MEDIA_BUS_FMT_SIGIG_GBGR_IGIG_GRGB10_1X10, { 10, "SIGIG_GBGR_IGIG_GRGB10_1X10", PixelFormatInfo::ColourEncodingRAW } }, - { MEDIA_BUS_FMT_SBGGR12_1X12, { 12, "SBGGR12_1X12", PixelFormatInfo::ColourEncodingRAW } }, - { MEDIA_BUS_FMT_SGBRG12_1X12, { 12, "SGBRG12_1X12", PixelFormatInfo::ColourEncodingRAW } }, - { MEDIA_BUS_FMT_SGRBG12_1X12, { 12, "SGRBG12_1X12", PixelFormatInfo::ColourEncodingRAW } }, --- -2.43.0 - diff --git a/modules/nixos/ipu6-softisp/libcamera/0021-libcamera-swstats_cpu-Add-support-for-10bpp-IGIG_GBG.patch b/modules/nixos/ipu6-softisp/libcamera/0021-libcamera-swstats_cpu-Add-support-for-10bpp-IGIG_GBG.patch deleted file mode 100644 index 44985a9..0000000 --- a/modules/nixos/ipu6-softisp/libcamera/0021-libcamera-swstats_cpu-Add-support-for-10bpp-IGIG_GBG.patch +++ /dev/null @@ -1,131 +0,0 @@ -From f939e68a3ef556e572f0140df6d7ef17d72f457e Mon Sep 17 00:00:00 2001 -From: Marttico -Date: Wed, 20 Dec 2023 20:26:15 +0100 -Subject: [PATCH 21/25] libcamera: swstats_cpu: Add support for 10bpp - IGIG_GBGR_IGIG_GRGB input - -Add support to SwStatsCpu for 10bpp IGIG_GBGR_IGIG_GRGB input -generated by the Omnivision ov01a1s sensor. - -Co-authored-by: Dennis Bonke -Signed-off-by: Dennis Bonke -Co-authored-by: Toon Langendam -Signed-off-by: Toon Langendam -Signed-off-by: Marttico -Signed-off-by: Hans de Goede ---- - .../internal/software_isp/swstats_cpu.h | 3 + - src/libcamera/software_isp/swstats_cpu.cpp | 76 +++++++++++++++++++ - 2 files changed, 79 insertions(+) - -diff --git a/include/libcamera/internal/software_isp/swstats_cpu.h b/include/libcamera/internal/software_isp/swstats_cpu.h -index e7abc6bb..a47241e1 100644 ---- a/include/libcamera/internal/software_isp/swstats_cpu.h -+++ b/include/libcamera/internal/software_isp/swstats_cpu.h -@@ -42,6 +42,9 @@ private: - /* Bayer 10 bpp packed */ - void statsBGGR10PLine0(const uint8_t *src[]); - void statsGBRG10PLine0(const uint8_t *src[]); -+ /* IGIG_GBGR_IGIG_GRGB 10 bpp unpacked */ -+ void statsRGBIR10Line0(const uint8_t *src[]); -+ void statsRGBIR10Line2(const uint8_t *src[]); - void resetStats(void); - void finishStats(void); - -diff --git a/src/libcamera/software_isp/swstats_cpu.cpp b/src/libcamera/software_isp/swstats_cpu.cpp -index 87550371..96e21be5 100644 ---- a/src/libcamera/software_isp/swstats_cpu.cpp -+++ b/src/libcamera/software_isp/swstats_cpu.cpp -@@ -187,6 +187,68 @@ void SwStatsCpu::statsGBRG10PLine0(const uint8_t *src[]) - statsBayer10P(window_.width, src0, src1, false, stats_); - } - -+void SwStatsCpu::statsRGBIR10Line0(const uint8_t *src[]) -+{ -+ const uint16_t *src0_16 = (const uint16_t *)src[2] + window_.x; -+ const uint16_t *src1_16 = (const uint16_t *)src[3] + window_.x; -+ uint16_t g3, g4; -+ -+ SWISP_LINARO_START_LINE_STATS(uint16_t) -+ -+ /* x += 8 sample every other 4x4 block */ -+ for (int x = 0; x < (int)window_.width; x += 8) { -+ /* IGIG */ -+ //i = src0_16[x]; -+ g2 = src0_16[x + 1]; -+ //i = src0_16[x + 2]; -+ g4 = src0_16[x + 3]; -+ -+ /* GBGR */ -+ g = src1_16[x]; -+ b = src1_16[x + 1]; -+ g3 = src1_16[x + 2]; -+ r = src1_16[x + 3]; -+ -+ g = (g + g2 + g3 + g4) / 4; -+ -+ /* divide Y by 4 for 10 -> 8 bpp value */ -+ SWISP_LINARO_ACCUMULATE_LINE_STATS(4) -+ } -+ -+ SWISP_LINARO_FINISH_LINE_STATS() -+} -+ -+void SwStatsCpu::statsRGBIR10Line2(const uint8_t *src[]) -+{ -+ const uint16_t *src0_16 = (const uint16_t *)src[2] + window_.x; -+ const uint16_t *src1_16 = (const uint16_t *)src[3] + window_.x; -+ uint16_t g3, g4; -+ -+ SWISP_LINARO_START_LINE_STATS(uint16_t) -+ -+ /* x += 8 sample every other 4x4 block */ -+ for (int x = 0; x < (int)window_.width; x += 8) { -+ /* IGIG */ -+ //i = src0_16[x]; -+ g2 = src0_16[x + 1]; -+ //i = src0_16[x + 2]; -+ g4 = src0_16[x + 3]; -+ -+ /* GRGB */ -+ g = src1_16[x]; -+ r = src1_16[x + 1]; -+ g3 = src1_16[x + 2]; -+ b = src1_16[x + 3]; -+ -+ g = (g + g2 + g3 + g4) / 4; -+ -+ /* divide Y by 4 for 10 -> 8 bpp value */ -+ SWISP_LINARO_ACCUMULATE_LINE_STATS(4) -+ } -+ -+ SWISP_LINARO_FINISH_LINE_STATS() -+} -+ - void SwStatsCpu::resetStats(void) - { - stats_.sumR_ = 0; -@@ -282,6 +344,20 @@ int SwStatsCpu::configure(const StreamConfiguration &inputCfg) - } - } - -+ if (bayerFormat.bitDepth == 10 && -+ bayerFormat.packing == BayerFormat::Packing::None && -+ bayerFormat.order == BayerFormat::IGIG_GBGR_IGIG_GRGB) { -+ bpp_ = 16; -+ patternSize_.height = 4; -+ patternSize_.width = 4; -+ y_skip_mask_ = 0x04; -+ x_shift_ = 0; -+ swap_lines_ = false; -+ stats0_ = (SwStats::statsProcessFn)&SwStatsCpu::statsRGBIR10Line0; -+ stats2_ = (SwStats::statsProcessFn)&SwStatsCpu::statsRGBIR10Line2; -+ return 0; -+ } -+ - LOG(SwStats, Info) - << "Unsupported input format " << inputCfg.pixelFormat.toString(); - return -EINVAL; --- -2.43.0 - diff --git a/modules/nixos/ipu6-softisp/libcamera/0022-libcamera-debayer_cpu-Add-support-for-10bpp-IGIG_GBG.patch b/modules/nixos/ipu6-softisp/libcamera/0022-libcamera-debayer_cpu-Add-support-for-10bpp-IGIG_GBG.patch deleted file mode 100644 index 4054a95..0000000 --- a/modules/nixos/ipu6-softisp/libcamera/0022-libcamera-debayer_cpu-Add-support-for-10bpp-IGIG_GBG.patch +++ /dev/null @@ -1,315 +0,0 @@ -From e3638943a8bd3f93b8d81c3996035c60755b97f6 Mon Sep 17 00:00:00 2001 -From: Marttico -Date: Wed, 20 Dec 2023 20:28:12 +0100 -Subject: [PATCH 22/25] libcamera: debayer_cpu: Add support for 10bpp - IGIG_GBGR_IGIG_GRGB input - -Add support to DebayerCpu for 10bpp IGIG_GBGR_IGIG_GRGB input -generated by the Omnivision ov01a1s sensor. - -Co-authored-by: Dennis Bonke -Signed-off-by: Dennis Bonke -Co-authored-by: Toon Langendam -Signed-off-by: Toon Langendam -Signed-off-by: Marttico -Signed-off-by: Hans de Goede ---- - .../internal/software_isp/debayer_cpu.h | 5 + - src/libcamera/software_isp/debayer_cpu.cpp | 251 ++++++++++++++++++ - 2 files changed, 256 insertions(+) - -diff --git a/include/libcamera/internal/software_isp/debayer_cpu.h b/include/libcamera/internal/software_isp/debayer_cpu.h -index bdeab7c0..52af117f 100644 ---- a/include/libcamera/internal/software_isp/debayer_cpu.h -+++ b/include/libcamera/internal/software_isp/debayer_cpu.h -@@ -96,6 +96,11 @@ private: - void debayer10P_GRGR_BGR888(uint8_t *dst, const uint8_t *src[]); - void debayer10P_GBGB_BGR888(uint8_t *dst, const uint8_t *src[]); - void debayer10P_RGRG_BGR888(uint8_t *dst, const uint8_t *src[]); -+ /* IGIG_GBGR_IGIG_GRGB unpacked 10-bit raw bayer format */ -+ void debayerIGIG10Line0(uint8_t *dst, const uint8_t *src[]); -+ void debayerGBGR10Line1(uint8_t *dst, const uint8_t *src[]); -+ void debayerIGIG10Line2(uint8_t *dst, const uint8_t *src[]); -+ void debayerGRGB10Line3(uint8_t *dst, const uint8_t *src[]); - - typedef void (DebayerCpu::*debayerFn)(uint8_t *dst, const uint8_t *src[]); - -diff --git a/src/libcamera/software_isp/debayer_cpu.cpp b/src/libcamera/software_isp/debayer_cpu.cpp -index 0edea4d3..41c8805f 100644 ---- a/src/libcamera/software_isp/debayer_cpu.cpp -+++ b/src/libcamera/software_isp/debayer_cpu.cpp -@@ -228,6 +228,238 @@ void DebayerCpu::debayer10P_RGRG_BGR888(uint8_t *dst, const uint8_t *src[]) - } - } - -+void DebayerCpu::debayerIGIG10Line0(uint8_t *dst, const uint8_t *src[]) -+{ -+ const uint16_t *prev = (const uint16_t *)src[1]; -+ const uint16_t *curr = (const uint16_t *)src[2]; -+ const uint16_t *next = (const uint16_t *)src[3]; -+ -+ for (int x = 0; x < (int)window_.width;) { -+ /* -+ * IGIG line pixel 0: IGIGI -+ * GBGRG -+ * IGIGI -+ * GRGBG -+ * IGIGI -+ */ -+ *dst++ = blue_[(prev[x - 1] + next[x + 1]) / 8]; -+ *dst++ = green_[(curr[x - 1] + curr[x + 1] + prev[x] + next[x]) / 16]; -+ *dst++ = red_[(prev[x + 1] + next[x - 1]) / 8]; -+ x++; -+ -+ /* -+ * IGIG line pixel 1: GIGIG -+ * BGRGB -+ * GIGIG -+ * RGBGR -+ * GIGIG -+ */ -+ *dst++ = blue_[next[x] / 4]; -+ *dst++ = green_[curr[x] / 4]; -+ *dst++ = red_[prev[x] / 4]; -+ x++; -+ -+ /* -+ * IGIG line pixel 2: IGIGI -+ * GRGBG -+ * IGIGI -+ * GBGRG -+ * IGIGI -+ */ -+ *dst++ = blue_[(prev[x + 1] + next[x - 1]) / 8]; -+ *dst++ = green_[(curr[x - 1] + curr[x + 1] + prev[x] + next[x]) / 16]; -+ *dst++ = red_[(prev[x - 1] + next[x + 1]) / 8]; -+ x++; -+ -+ /* -+ * IGIG line pixel 3: GIGIG -+ * RGBGR -+ * GIGIG -+ * BGRGB -+ * GIGIG -+ */ -+ *dst++ = blue_[prev[x] / 4]; -+ *dst++ = green_[curr[x] / 4]; -+ *dst++ = red_[next[x] / 4]; -+ x++; -+ } -+} -+ -+void DebayerCpu::debayerGBGR10Line1(uint8_t *dst, const uint8_t *src[]) -+{ -+ const uint16_t *prev2 = (const uint16_t *)src[0]; -+ const uint16_t *prev = (const uint16_t *)src[1]; -+ const uint16_t *curr = (const uint16_t *)src[2]; -+ const uint16_t *next = (const uint16_t *)src[3]; -+ const uint16_t *next2 = (const uint16_t *)src[4]; -+ -+ for (int x = 0; x < (int)window_.width;) { -+ /* -+ * GBGR line pixel 0: GBGRG -+ * IGIGI -+ * GRGBG -+ * IGIGI -+ * GBGRG -+ */ -+ *dst++ = blue_[curr[x + 1] / 4]; -+ *dst++ = green_[curr[x] / 4]; -+ *dst++ = red_[curr[x - 1] / 4]; -+ x++; -+ -+ /* -+ * GBGR line pixel 1: BGRGB -+ * GIGIG -+ * RGBGR -+ * GIGIG -+ * BGRGB -+ */ -+ *dst++ = blue_[curr[x] / 4]; -+ *dst++ = green_[(curr[x - 1] + curr[x + 1] + prev[x] + next[x]) / 16]; -+ *dst++ = red_[(curr[x - 2] + curr[x + 2] + prev2[x] + next2[x]) / 16]; -+ x++; -+ -+ /* -+ * GBGR line pixel 2: GRGBG -+ * IGIGI -+ * GBGRG -+ * IGIGI -+ * GRGBG -+ */ -+ *dst++ = blue_[curr[x - 1] / 4]; -+ *dst++ = green_[curr[x] / 4]; -+ *dst++ = red_[curr[x + 1] / 4]; -+ x++; -+ -+ /* -+ * GBGR line pixel 3: RGBGR -+ * GIGIG -+ * BGRGB -+ * GIGIG -+ * RGBGR -+ */ -+ *dst++ = blue_[(curr[x - 2] + curr[x + 2] + prev2[x] + next2[x]) / 16]; -+ *dst++ = green_[(curr[x - 1] + curr[x + 1] + prev[x] + next[x]) / 16]; -+ *dst++ = red_[curr[x] / 4]; -+ x++; -+ } -+} -+ -+void DebayerCpu::debayerIGIG10Line2(uint8_t *dst, const uint8_t *src[]) -+{ -+ const uint16_t *prev = (const uint16_t *)src[1]; -+ const uint16_t *curr = (const uint16_t *)src[2]; -+ const uint16_t *next = (const uint16_t *)src[3]; -+ -+ for (int x = 0; x < (int)window_.width;) { -+ /* -+ * IGIG line pixel 0: IGIGI -+ * GRGBG -+ * IGIGI -+ * GBGRG -+ * IGIGI -+ */ -+ *dst++ = blue_[(prev[x + 1] + next[x - 1]) / 8]; -+ *dst++ = green_[(curr[x - 1] + curr[x + 1] + prev[x] + next[x]) / 16]; -+ *dst++ = red_[(prev[x - 1] + next[x + 1]) / 8]; -+ x++; -+ -+ /* -+ * IGIG line pixel 1: GIGIG -+ * RGBGR -+ * GIGIG -+ * BGRGB -+ * GIGIG -+ */ -+ *dst++ = blue_[prev[x] / 4]; -+ *dst++ = green_[curr[x] / 4]; -+ *dst++ = red_[next[x] / 4]; -+ x++; -+ -+ /* -+ * IGIG line pixel 2: IGIGI -+ * GBGRG -+ * IGIGI -+ * GRGBG -+ * IGIGI -+ */ -+ *dst++ = blue_[(prev[x - 1] + next[x + 1]) / 8]; -+ *dst++ = green_[(curr[x - 1] + curr[x + 1] + prev[x] + next[x]) / 16]; -+ *dst++ = red_[(prev[x + 1] + next[x - 1]) / 8]; -+ x++; -+ -+ /* -+ * IGIG line pixel 3: GIGIG -+ * BGRGB -+ * GIGIG -+ * RGBGR -+ * GIGIG -+ */ -+ *dst++ = blue_[next[x] / 4]; -+ *dst++ = green_[curr[x] / 4]; -+ *dst++ = red_[prev[x] / 4]; -+ x++; -+ } -+} -+ -+void DebayerCpu::debayerGRGB10Line3(uint8_t *dst, const uint8_t *src[]) -+{ -+ const uint16_t *prev2 = (const uint16_t *)src[0]; -+ const uint16_t *prev = (const uint16_t *)src[1]; -+ const uint16_t *curr = (const uint16_t *)src[2]; -+ const uint16_t *next = (const uint16_t *)src[3]; -+ const uint16_t *next2 = (const uint16_t *)src[4]; -+ -+ for (int x = 0; x < (int)window_.width;) { -+ /* -+ * GRGB line pixel 0: GRGBG -+ * IGIGI -+ * GBGRG -+ * IGIGI -+ * GRGBG -+ */ -+ *dst++ = blue_[curr[x - 1] / 4]; -+ *dst++ = green_[curr[x] / 4]; -+ *dst++ = red_[curr[x + 1] / 4]; -+ x++; -+ -+ /* -+ * GRGB line pixel 1: RGBGR -+ * GIGIG -+ * BGRGB -+ * GIGIG -+ * RGBGR -+ */ -+ *dst++ = blue_[(curr[x - 2] + curr[x + 2] + prev2[x] + next2[x]) / 16]; -+ *dst++ = green_[(curr[x - 1] + curr[x + 1] + prev[x] + next[x]) / 16]; -+ *dst++ = red_[curr[x] / 4]; -+ x++; -+ -+ /* -+ * GRGB line pixel 2: GBGRG -+ * IGIGI -+ * GRGBG -+ * IGIGI -+ * GBGRG -+ */ -+ *dst++ = blue_[curr[x + 1] / 4]; -+ *dst++ = green_[curr[x] / 4]; -+ *dst++ = red_[curr[x - 1] / 4]; -+ x++; -+ -+ /* -+ * GRGB line pixel 3: BGRGB -+ * GIGIG -+ * RGBGR -+ * GIGIG -+ * BGRGB -+ */ -+ *dst++ = blue_[curr[x] / 4]; -+ *dst++ = green_[(curr[x - 1] + curr[x + 1] + prev[x] + next[x]) / 16]; -+ *dst++ = red_[(curr[x - 2] + curr[x + 2] + prev2[x] + next2[x]) / 16]; -+ x++; -+ } -+} -+ - static bool isStandardBayerOrder(BayerFormat::Order order) - { - return order == BayerFormat::BGGR || order == BayerFormat::GBRG || -@@ -259,6 +491,15 @@ int DebayerCpu::getInputConfig(PixelFormat inputFormat, DebayerInputConfig &conf - return 0; - } - -+ if (bayerFormat.bitDepth == 10 && bayerFormat.packing == BayerFormat::Packing::None && -+ bayerFormat.order == BayerFormat::IGIG_GBGR_IGIG_GRGB) { -+ config.bpp = 16; -+ config.patternSize.height = 4; -+ config.patternSize.width = 4; -+ config.outputFormats = std::vector({ formats::RGB888 }); -+ return 0; -+ } -+ - LOG(Debayer, Info) - << "Unsupported input format " << inputFormat.toString(); - return -EINVAL; -@@ -384,6 +625,16 @@ int DebayerCpu::setDebayerFunctions(PixelFormat inputFormat, PixelFormat outputF - } - } - -+ if (bayerFormat.bitDepth == 10 && -+ bayerFormat.packing == BayerFormat::Packing::None && -+ bayerFormat.order == BayerFormat::IGIG_GBGR_IGIG_GRGB) { -+ debayer0_ = &DebayerCpu::debayerIGIG10Line0; -+ debayer1_ = &DebayerCpu::debayerGBGR10Line1; -+ debayer2_ = &DebayerCpu::debayerIGIG10Line2; -+ debayer3_ = &DebayerCpu::debayerGRGB10Line3; -+ return 0; -+ } -+ - invalid_fmt: - LOG(Debayer, Error) << "Unsupported input output format combination"; - return -EINVAL; --- -2.43.0 - diff --git a/modules/nixos/ipu6-softisp/libcamera/0023-libcamera-Add-Software-ISP-benchmarking-documentatio.patch b/modules/nixos/ipu6-softisp/libcamera/0023-libcamera-Add-Software-ISP-benchmarking-documentatio.patch deleted file mode 100644 index ec49917..0000000 --- a/modules/nixos/ipu6-softisp/libcamera/0023-libcamera-Add-Software-ISP-benchmarking-documentatio.patch +++ /dev/null @@ -1,130 +0,0 @@ -From 26e96232c314f9d34f6ee3be365c04918967084e Mon Sep 17 00:00:00 2001 -From: Hans de Goede -Date: Mon, 22 Jan 2024 17:18:00 +0100 -Subject: [PATCH 23/25] libcamera: Add "Software ISP benchmarking" - documentation - -Add a "Software ISP benchmarking" documentation section which describes -the performance/power consumption measurements used during -the Software ISP's development. - -Signed-off-by: Hans de Goede ---- - 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 - Python Bindings - Camera Sensor Model -+ SoftwareISP Benchmarking -diff --git a/Documentation/meson.build b/Documentation/meson.build -index 7a58fec8..3872e0a8 100644 ---- a/Documentation/meson.build -+++ b/Documentation/meson.build -@@ -80,6 +80,7 @@ if sphinx.found() - 'lens_driver_requirements.rst', - 'python-bindings.rst', - 'sensor_driver_requirements.rst', -+ 'software-isp-benchmarking.rst', - '../README.rst', - ] - -diff --git a/Documentation/software-isp-benchmarking.rst b/Documentation/software-isp-benchmarking.rst -new file mode 100644 -index 00000000..738c8c65 ---- /dev/null -+++ b/Documentation/software-isp-benchmarking.rst -@@ -0,0 +1,82 @@ -+.. SPDX-License-Identifier: CC-BY-SA-4.0 -+ -+.. _software-isp-benchmarking: -+ -+Software ISP benchmarking -+========================= -+ -+The Software ISP is paricular sensitive to performance regressions -+therefor it is a good idea to always benchmark the Software ISP -+before and after making changes to it and ensure that there are -+no performance regressions. -+ -+DebayerCpu class builtin benchmark -+---------------------------------- -+ -+The DebayerCpu class has a builtin benchmark. This benchmark -+measures the time spend on processing (collecting statistics -+and debayering) only, it does not measure the time spend on -+capturing or outputting the frames. -+ -+The builtin benchmark always runs. So this can be used by simply -+running "cam" or "qcam" with a pipeline using the Software ISP. -+ -+When it runs it will skip measuring the first 30 frames to -+allow the caches and the CPU temperature (turbo-ing) to warm-up -+and then it measures 30 fps and shows the total and per frame -+processing time using an info level log message: -+ -+.. code-block:: -+ -+ INFO Debayer debayer_cpu.cpp:907 Processed 30 frames in 244317us, 8143 us/frame -+ -+To get stable measurements it is advised to disable any other processes which -+may cause significant CPU usage (e.g. disable wifi, bluetooth and browsers). -+When possible it is also advisable to disable CPU turbo-ing and -+frequency-scaling. -+ -+For example when benchmarking on a Lenovo ThinkPad X1 Yoga Gen 8, with -+the charger plugged in, the CPU can be fixed to run at 2 GHz using: -+ -+.. code-block:: -+ -+ sudo x86_energy_perf_policy --turbo-enable 0 -+ sudo cpupower frequency-set -d 2GHz -u 2GHz -+ -+with these settings the builtin bench reports a processing time of ~7.8ms/frame -+on this laptop for FHD SGRBG10 (unpacked) bayer data. -+ -+Measuring power consumption -+--------------------------- -+ -+Since the Software ISP is often used on mobile devices it is also -+important to measure power consumption and ensure that that does -+not regress. -+ -+For example to measure power consumption on a Lenovo ThinkPad X1 Yoga Gen 8 -+it needs to be running on battery and it should be configured with its -+platform-profile (/sys/firmware/acpi/platform_profile) set to balanced and -+with its default turbo and frequency-scaling behavior to match real world usage. -+ -+Then start qcam to capture a FHD picture at 30 fps and position the qcam window -+so that it is fully visible. After this run the following command to monitor -+the power consumption: -+ -+.. code-block:: -+ -+ watch -n 10 cat /sys/class/power_supply/BAT0/power_now /sys/class/hwmon/hwmon6/fan?_input -+ -+Note this not only measures the power consumption in ųW it also monitors -+the speed of this laptop's 2 fans. This is important because depending on -+the ambient temperature the 2 fans may spin up while testing and this -+will cause an additional power consumption of approx. 0.5W messing up -+the measurement. -+ -+After starting qcam + the watch command let the laptop sit without using -+it for 2 minutes for the readings to stabilize. Then check that the fans -+have not turned on and manually take a couple of consecutive power readings -+and avarage these. -+ -+On the example Lenovo ThinkPad X1 Yoga Gen 8 laptop this results in -+a measured power consumption of approx. 13W while running qcam versus -+approx. 4-5W while setting idle with its OLED panel on. --- -2.43.0 - diff --git a/modules/nixos/ipu6-softisp/libcamera/0024-ov01a1s-HACK.patch b/modules/nixos/ipu6-softisp/libcamera/0024-ov01a1s-HACK.patch deleted file mode 100644 index 3b558e0..0000000 --- a/modules/nixos/ipu6-softisp/libcamera/0024-ov01a1s-HACK.patch +++ /dev/null @@ -1,95 +0,0 @@ -From 9bec33e5c7e6765734eeef2d22d7f7f65dee2264 Mon Sep 17 00:00:00 2001 -From: Hans de Goede -Date: Tue, 19 Dec 2023 15:45:51 +0100 -Subject: [PATCH 24/25] ov01a1s HACK - -Signed-off-by: Hans de Goede ---- - src/libcamera/camera_sensor.cpp | 6 ++++++ - src/libcamera/software_isp/debayer_cpu.cpp | 8 ++++++++ - src/libcamera/software_isp/swstats_cpu.cpp | 5 +++++ - 3 files changed, 19 insertions(+) - -diff --git a/src/libcamera/camera_sensor.cpp b/src/libcamera/camera_sensor.cpp -index f19f72ea..7ad4b9ef 100644 ---- a/src/libcamera/camera_sensor.cpp -+++ b/src/libcamera/camera_sensor.cpp -@@ -34,6 +34,9 @@ - - namespace libcamera { - -+// HACK HACK -+bool is_ov01a1s = false; -+ - LOG_DEFINE_CATEGORY(CameraSensor) - - /** -@@ -426,6 +429,9 @@ int CameraSensor::initProperties() - model_ = subdev_->model(); - properties_.set(properties::Model, utils::toAscii(model_)); - -+ if (model_ == "ov01a1s") -+ is_ov01a1s = true; -+ - /* Generate a unique ID for the sensor. */ - int ret = generateId(); - if (ret) -diff --git a/src/libcamera/software_isp/debayer_cpu.cpp b/src/libcamera/software_isp/debayer_cpu.cpp -index 41c8805f..b6393925 100644 ---- a/src/libcamera/software_isp/debayer_cpu.cpp -+++ b/src/libcamera/software_isp/debayer_cpu.cpp -@@ -23,6 +23,8 @@ - - namespace libcamera { - -+extern bool is_ov01a1s; -+ - DebayerCpu::DebayerCpu(std::unique_ptr stats) - : stats_(std::move(stats)), gamma_correction_(1.0) - { -@@ -471,6 +473,9 @@ int DebayerCpu::getInputConfig(PixelFormat inputFormat, DebayerInputConfig &conf - BayerFormat bayerFormat = - BayerFormat::fromPixelFormat(inputFormat); - -+ if (is_ov01a1s) -+ bayerFormat.order = BayerFormat::IGIG_GBGR_IGIG_GRGB; -+ - if ((bayerFormat.bitDepth == 8 || bayerFormat.bitDepth == 10 || bayerFormat.bitDepth == 12) && - bayerFormat.packing == BayerFormat::Packing::None && - isStandardBayerOrder(bayerFormat.order)) { -@@ -548,6 +553,9 @@ int DebayerCpu::setDebayerFunctions(PixelFormat inputFormat, PixelFormat outputF - BayerFormat bayerFormat = - BayerFormat::fromPixelFormat(inputFormat); - -+ if (is_ov01a1s) -+ bayerFormat.order = BayerFormat::IGIG_GBGR_IGIG_GRGB; -+ - swapRedBlueGains_ = false; - - switch (outputFormat) { -diff --git a/src/libcamera/software_isp/swstats_cpu.cpp b/src/libcamera/software_isp/swstats_cpu.cpp -index 96e21be5..503ce799 100644 ---- a/src/libcamera/software_isp/swstats_cpu.cpp -+++ b/src/libcamera/software_isp/swstats_cpu.cpp -@@ -19,6 +19,8 @@ - - namespace libcamera { - -+extern bool is_ov01a1s; -+ - SwStatsCpu::SwStatsCpu() - : SwStats() - { -@@ -301,6 +303,9 @@ int SwStatsCpu::configure(const StreamConfiguration &inputCfg) - BayerFormat bayerFormat = - BayerFormat::fromPixelFormat(inputCfg.pixelFormat); - -+ if (is_ov01a1s) -+ bayerFormat.order = BayerFormat::IGIG_GBGR_IGIG_GRGB; -+ - startFrame_ = (SwStats::statsVoidFn)&SwStatsCpu::resetStats; - finishFrame_ = (SwStats::statsVoidFn)&SwStatsCpu::finishStats; - --- -2.43.0 - diff --git a/modules/nixos/ipu6-softisp/libcamera/0025-libcamera-debayer_cpu-Make-the-minimum-size-1280x720.patch b/modules/nixos/ipu6-softisp/libcamera/0025-libcamera-debayer_cpu-Make-the-minimum-size-1280x720.patch deleted file mode 100644 index 1f26735..0000000 --- a/modules/nixos/ipu6-softisp/libcamera/0025-libcamera-debayer_cpu-Make-the-minimum-size-1280x720.patch +++ /dev/null @@ -1,40 +0,0 @@ -From 4f2c94ba8b7f9f4d85a1d7e03f4c5272d92c3361 Mon Sep 17 00:00:00 2001 -From: Hans de Goede -Date: Wed, 24 Jan 2024 20:44:29 +0100 -Subject: [PATCH 25/25] libcamera: debayer_cpu: Make the minimum size 1280x720 - -pipewire + firefox default to what looks like 640x480 if we export -the entire supported cropping range. Hardcode 720p as minsize for now. - -Signed-off-by: Hans de Goede ---- - include/libcamera/internal/software_isp/debayer.h | 13 ++++++++++--- - 1 file changed, 10 insertions(+), 3 deletions(-) - -diff --git a/include/libcamera/internal/software_isp/debayer.h b/include/libcamera/internal/software_isp/debayer.h -index 39e6f393..4348173d 100644 ---- a/include/libcamera/internal/software_isp/debayer.h -+++ b/include/libcamera/internal/software_isp/debayer.h -@@ -112,9 +112,16 @@ public: - return {}; - } - -- return SizeRange(Size(pattern_size.width, pattern_size.height), -- Size((inputSize.width - 2 * pattern_size.width) & ~(pattern_size.width - 1), -- (inputSize.height - 2 * pattern_size.height) & ~(pattern_size.height - 1)), -+ /* -+ * pipewire + firefox default to what looks like 640x480 -+ * if we export the entire supported cropping range. -+ * Hardcode 720p as minsize for now. Minsize should be -+ * Size(pattern_size.width, pattern_size.height) -+ */ -+ unsigned int w = (inputSize.width - 2 * pattern_size.width) & ~(pattern_size.width - 1); -+ unsigned int h = (inputSize.height - 2 * pattern_size.height) & ~(pattern_size.height - 1); -+ return SizeRange(Size(std::min(w, 1280u), std::min(h, 720u)), -+ Size(w, h), - pattern_size.width, pattern_size.height); - } - --- -2.43.0 - diff --git a/overlays/ipu6/default.nix b/overlays/ipu6/default.nix deleted file mode 100644 index 83f5336..0000000 --- a/overlays/ipu6/default.nix +++ /dev/null @@ -1,6 +0,0 @@ -{ channels, ... }: -final: prev: -{ - libcamera-unstable = channels.unstable.libcamera; - pipewire-unstable = channels.unstable.pipewire; -} diff --git a/systems/x86_64-linux/x1/default.nix b/systems/x86_64-linux/x1/default.nix index 133be4e..4c2616c 100644 --- a/systems/x86_64-linux/x1/default.nix +++ b/systems/x86_64-linux/x1/default.nix @@ -5,7 +5,6 @@ with lib.plusultra; imports = [ ./hardware-configuration.nix ]; plusultra = { - ipu6.enable = true; base.enable = true; gui.enable = true; nix-ld.enable = true;