diff options
author | Florian Klink <flokli@flokli.de> | 2024-02-13T10·49+0200 |
---|---|---|
committer | clbot <clbot@tvl.fyi> | 2024-02-13T15·37+0000 |
commit | 41cd3c44d2f76a0df0e279609c17ef62e7e622f4 (patch) | |
tree | 21f631381e86af0ebe9b8ca2703f4a4e2fe0ff7a /users/flokli | |
parent | 28173ca4b92d5bb723fe77ae217bcf4f3bb6ce0d (diff) |
chore(users/flokli/ipu6-softisp): inline kernel patches r/7504
"media: ov2740: Fix hts value" got backported into linux-stable: ``` commit c5883a8a3676b94fe7cefde97c8eec50bb879eb2 Author: Hans de Goede <hdegoede@redhat.com> Date: Mon Dec 4 13:39:42 2023 +0100 media: ov2740: Fix hts value [ Upstream commit 3735228bbe3511f844e03dfcc4003fadb59dde23 ] HTS must be more then width, so the 1080 value clearly is wrong, this is then corrected with some weird math dividing clocks in to_pixels_per_line() which results in the hts getting multiplied by 2, resulting in 2160. Instead just directly set hts to the correct value of 2160 and drop to_pixels_per_line(). Signed-off-by: Hans de Goede <hdegoede@redhat.com> Signed-off-by: Sakari Ailus <sakari.ailus@linux.intel.com> Signed-off-by: Hans Verkuil <hverkuil-cisco@xs4all.nl> Signed-off-by: Sasha Levin <sashal@kernel.org> ``` So drop that patch from the list of kernel patches to apply (and vendor in the patches). Change-Id: I314ccb524d156c0f445ed923ce0600f27b8cc699 Reviewed-on: https://cl.tvl.fyi/c/depot/+/10813 Tested-by: BuildkiteCI Autosubmit: flokli <flokli@flokli.de> Reviewed-by: flokli <flokli@flokli.de>
Diffstat (limited to 'users/flokli')
-rw-r--r-- | users/flokli/ipu6-softisp/config.nix | 5 | ||||
-rw-r--r-- | users/flokli/ipu6-softisp/kernel/softisp.patch | 17143 |
2 files changed, 17144 insertions, 4 deletions
diff --git a/users/flokli/ipu6-softisp/config.nix b/users/flokli/ipu6-softisp/config.nix index 9232d5bc9ee5..10244f91a823 100644 --- a/users/flokli/ipu6-softisp/config.nix +++ b/users/flokli/ipu6-softisp/config.nix @@ -72,10 +72,7 @@ in boot.kernelPatches = [{ name = "linux-kernel-test.patch"; - patch = pkgs.fetchurl { - url = "https://copr-dist-git.fedorainfracloud.org/cgit/jwrdegoede/ipu6-softisp/kernel.git/plain/linux-kernel-test.patch?h=f39&id=0ed76891b2fc08579d08bedb9294a41840007299"; - hash = "sha256-8hKP4nltGlkzr8iOgsIUT9Tt5i+x4kdLmw/+lFeNoGQ="; - }; + patch = ./kernel/softisp.patch; extraStructuredConfig = { # needed for /dev/dma_heap DMABUF_HEAPS_CMA = lib.kernel.yes; diff --git a/users/flokli/ipu6-softisp/kernel/softisp.patch b/users/flokli/ipu6-softisp/kernel/softisp.patch new file mode 100644 index 000000000000..b94126b53ad1 --- /dev/null +++ b/users/flokli/ipu6-softisp/kernel/softisp.patch @@ -0,0 +1,17143 @@ +From 4e34bb68beec288e6fbc71170ff6d3ed39b76ce6 Mon Sep 17 00:00:00 2001 +From: Bingbu Cao <bingbu.cao@intel.com> +Date: Thu, 11 Jan 2024 14:55:15 +0800 +Subject: [PATCH 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 <bingbu.cao@intel.com> +Link: https://lore.kernel.org/r/20240111065531.2418836-2-bingbu.cao@intel.com +Signed-off-by: Hans de Goede <hdegoede@redhat.com> +--- + .../media/pci/intel/ipu6/ipu6-platform-regs.h | 179 ++++ + drivers/media/pci/intel/ipu6/ipu6.c | 966 ++++++++++++++++++ + drivers/media/pci/intel/ipu6/ipu6.h | 356 +++++++ + 3 files changed, 1501 insertions(+) + create mode 100644 drivers/media/pci/intel/ipu6/ipu6-platform-regs.h + create mode 100644 drivers/media/pci/intel/ipu6/ipu6.c + create mode 100644 drivers/media/pci/intel/ipu6/ipu6.h + +diff --git a/drivers/media/pci/intel/ipu6/ipu6-platform-regs.h b/drivers/media/pci/intel/ipu6/ipu6-platform-regs.h +new file mode 100644 +index 000000000000..cae26009c9fc +--- /dev/null ++++ b/drivers/media/pci/intel/ipu6/ipu6-platform-regs.h +@@ -0,0 +1,179 @@ ++/* SPDX-License-Identifier: GPL-2.0-only */ ++/* Copyright (C) 2018 - 2023 Intel Corporation */ ++ ++#ifndef IPU6_PLATFORM_REGS_H ++#define IPU6_PLATFORM_REGS_H ++ ++#include <linux/bits.h> ++ ++/* ++ * IPU6 uses uniform address within IPU6, therefore all subsystem registers ++ * locates in one single space starts from 0 but in different sctions with ++ * different addresses, the subsystem offsets are defined to 0 as the ++ * register definition will have the address offset to 0. ++ */ ++#define IPU6_UNIFIED_OFFSET 0 ++ ++#define IPU6_ISYS_IOMMU0_OFFSET 0x2e0000 ++#define IPU6_ISYS_IOMMU1_OFFSET 0x2e0500 ++#define IPU6_ISYS_IOMMUI_OFFSET 0x2e0a00 ++ ++#define IPU6_PSYS_IOMMU0_OFFSET 0x1b0000 ++#define IPU6_PSYS_IOMMU1_OFFSET 0x1b0700 ++#define IPU6_PSYS_IOMMU1R_OFFSET 0x1b0e00 ++#define IPU6_PSYS_IOMMUI_OFFSET 0x1b1500 ++ ++/* the offset from IOMMU base register */ ++#define IPU6_MMU_L1_STREAM_ID_REG_OFFSET 0x0c ++#define IPU6_MMU_L2_STREAM_ID_REG_OFFSET 0x4c ++#define IPU6_PSYS_MMU1W_L2_STREAM_ID_REG_OFFSET 0x8c ++ ++#define IPU6_MMU_INFO_OFFSET 0x8 ++ ++#define IPU6_ISYS_SPC_OFFSET 0x210000 ++ ++#define IPU6SE_PSYS_SPC_OFFSET 0x110000 ++#define IPU6_PSYS_SPC_OFFSET 0x118000 ++ ++#define IPU6_ISYS_DMEM_OFFSET 0x200000 ++#define IPU6_PSYS_DMEM_OFFSET 0x100000 ++ ++#define IPU6_REG_ISYS_UNISPART_IRQ_EDGE 0x27c000 ++#define IPU6_REG_ISYS_UNISPART_IRQ_MASK 0x27c004 ++#define IPU6_REG_ISYS_UNISPART_IRQ_STATUS 0x27c008 ++#define IPU6_REG_ISYS_UNISPART_IRQ_CLEAR 0x27c00c ++#define IPU6_REG_ISYS_UNISPART_IRQ_ENABLE 0x27c010 ++#define IPU6_REG_ISYS_UNISPART_IRQ_LEVEL_NOT_PULSE 0x27c014 ++#define IPU6_REG_ISYS_UNISPART_SW_IRQ_REG 0x27c414 ++#define IPU6_REG_ISYS_UNISPART_SW_IRQ_MUX_REG 0x27c418 ++#define IPU6_ISYS_UNISPART_IRQ_CSI0 BIT(2) ++#define IPU6_ISYS_UNISPART_IRQ_CSI1 BIT(3) ++#define IPU6_ISYS_UNISPART_IRQ_SW BIT(22) ++ ++#define IPU6_REG_ISYS_ISL_TOP_IRQ_EDGE 0x2b0200 ++#define IPU6_REG_ISYS_ISL_TOP_IRQ_MASK 0x2b0204 ++#define IPU6_REG_ISYS_ISL_TOP_IRQ_STATUS 0x2b0208 ++#define IPU6_REG_ISYS_ISL_TOP_IRQ_CLEAR 0x2b020c ++#define IPU6_REG_ISYS_ISL_TOP_IRQ_ENABLE 0x2b0210 ++#define IPU6_REG_ISYS_ISL_TOP_IRQ_LEVEL_NOT_PULSE 0x2b0214 ++ ++#define IPU6_REG_ISYS_CMPR_TOP_IRQ_EDGE 0x2d2100 ++#define IPU6_REG_ISYS_CMPR_TOP_IRQ_MASK 0x2d2104 ++#define IPU6_REG_ISYS_CMPR_TOP_IRQ_STATUS 0x2d2108 ++#define IPU6_REG_ISYS_CMPR_TOP_IRQ_CLEAR 0x2d210c ++#define IPU6_REG_ISYS_CMPR_TOP_IRQ_ENABLE 0x2d2110 ++#define IPU6_REG_ISYS_CMPR_TOP_IRQ_LEVEL_NOT_PULSE 0x2d2114 ++ ++/* CDC Burst collector thresholds for isys - 3 FIFOs i = 0..2 */ ++#define IPU6_REG_ISYS_CDC_THRESHOLD(i) (0x27c400 + ((i) * 4)) ++ ++#define IPU6_CSI_IRQ_NUM_PER_PIPE 4 ++#define IPU6SE_ISYS_CSI_PORT_NUM 4 ++#define IPU6_ISYS_CSI_PORT_NUM 8 ++ ++#define IPU6_ISYS_CSI_PORT_IRQ(irq_num) BIT(irq_num) ++ ++/* PKG DIR OFFSET in IMR in secure mode */ ++#define IPU6_PKG_DIR_IMR_OFFSET 0x40 ++ ++#define IPU6_ISYS_REG_SPC_STATUS_CTRL 0x0 ++ ++#define IPU6_ISYS_SPC_STATUS_START BIT(1) ++#define IPU6_ISYS_SPC_STATUS_RUN BIT(3) ++#define IPU6_ISYS_SPC_STATUS_READY BIT(5) ++#define IPU6_ISYS_SPC_STATUS_CTRL_ICACHE_INVALIDATE BIT(12) ++#define IPU6_ISYS_SPC_STATUS_ICACHE_PREFETCH BIT(13) ++ ++#define IPU6_PSYS_REG_SPC_STATUS_CTRL 0x0 ++#define IPU6_PSYS_REG_SPC_START_PC 0x4 ++#define IPU6_PSYS_REG_SPC_ICACHE_BASE 0x10 ++#define IPU6_REG_PSYS_INFO_SEG_0_CONFIG_ICACHE_MASTER 0x14 ++ ++#define IPU6_PSYS_SPC_STATUS_START BIT(1) ++#define IPU6_PSYS_SPC_STATUS_RUN BIT(3) ++#define IPU6_PSYS_SPC_STATUS_READY BIT(5) ++#define IPU6_PSYS_SPC_STATUS_CTRL_ICACHE_INVALIDATE BIT(12) ++#define IPU6_PSYS_SPC_STATUS_ICACHE_PREFETCH BIT(13) ++ ++#define IPU6_PSYS_REG_SPP0_STATUS_CTRL 0x20000 ++ ++#define IPU6_INFO_ENABLE_SNOOP BIT(0) ++#define IPU6_INFO_DEC_FORCE_FLUSH BIT(1) ++#define IPU6_INFO_DEC_PASS_THROUGH BIT(2) ++#define IPU6_INFO_ZLW BIT(3) ++#define IPU6_INFO_REQUEST_DESTINATION_IOSF BIT(9) ++#define IPU6_INFO_IMR_BASE BIT(10) ++#define IPU6_INFO_IMR_DESTINED BIT(11) ++ ++#define IPU6_INFO_REQUEST_DESTINATION_PRIMARY IPU6_INFO_REQUEST_DESTINATION_IOSF ++ ++/* ++ * s2m_pixel_soc_pixel_remapping is dedicated for the enabling of the ++ * pixel s2m remp ability.Remap here means that s2m rearange the order ++ * of the pixels in each 4 pixels group. ++ * For examle, mirroring remping means that if input's 4 first pixels ++ * are 1 2 3 4 then in output we should see 4 3 2 1 in this 4 first pixels. ++ * 0xE4 is from s2m MAS document. It means no remapping. ++ */ ++#define S2M_PIXEL_SOC_PIXEL_REMAPPING_FLAG_NO_REMAPPING 0xe4 ++/* ++ * csi_be_soc_pixel_remapping is for the enabling of the pixel remapping. ++ * This remapping is exactly like the stream2mmio remapping. ++ */ ++#define CSI_BE_SOC_PIXEL_REMAPPING_FLAG_NO_REMAPPING 0xe4 ++ ++#define IPU6_REG_DMA_TOP_AB_GROUP1_BASE_ADDR 0x1ae000 ++#define IPU6_REG_DMA_TOP_AB_GROUP2_BASE_ADDR 0x1af000 ++#define IPU6_REG_DMA_TOP_AB_RING_MIN_OFFSET(n) (0x4 + (n) * 0xc) ++#define IPU6_REG_DMA_TOP_AB_RING_MAX_OFFSET(n) (0x8 + (n) * 0xc) ++#define IPU6_REG_DMA_TOP_AB_RING_ACCESS_OFFSET(n) (0xc + (n) * 0xc) ++ ++enum ipu6_device_ab_group1_target_id { ++ IPU6_DEVICE_AB_GROUP1_TARGET_ID_R0_SPC_DMEM, ++ IPU6_DEVICE_AB_GROUP1_TARGET_ID_R1_SPC_DMEM, ++ IPU6_DEVICE_AB_GROUP1_TARGET_ID_R2_SPC_DMEM, ++ IPU6_DEVICE_AB_GROUP1_TARGET_ID_R3_SPC_STATUS_REG, ++ IPU6_DEVICE_AB_GROUP1_TARGET_ID_R4_SPC_MASTER_BASE_ADDR, ++ IPU6_DEVICE_AB_GROUP1_TARGET_ID_R5_SPC_PC_STALL, ++ IPU6_DEVICE_AB_GROUP1_TARGET_ID_R6_SPC_EQ, ++ IPU6_DEVICE_AB_GROUP1_TARGET_ID_R7_SPC_RESERVED, ++ IPU6_DEVICE_AB_GROUP1_TARGET_ID_R8_SPC_RESERVED, ++ IPU6_DEVICE_AB_GROUP1_TARGET_ID_R9_SPP0, ++ IPU6_DEVICE_AB_GROUP1_TARGET_ID_R10_SPP1, ++ IPU6_DEVICE_AB_GROUP1_TARGET_ID_R11_CENTRAL_R1, ++ IPU6_DEVICE_AB_GROUP1_TARGET_ID_R12_IRQ, ++ IPU6_DEVICE_AB_GROUP1_TARGET_ID_R13_CENTRAL_R2, ++ IPU6_DEVICE_AB_GROUP1_TARGET_ID_R14_DMA, ++ IPU6_DEVICE_AB_GROUP1_TARGET_ID_R15_DMA, ++ IPU6_DEVICE_AB_GROUP1_TARGET_ID_R16_GP, ++ IPU6_DEVICE_AB_GROUP1_TARGET_ID_R17_ZLW_INSERTER, ++ IPU6_DEVICE_AB_GROUP1_TARGET_ID_R18_AB, ++}; ++ ++enum nci_ab_access_mode { ++ NCI_AB_ACCESS_MODE_RW, /* read & write */ ++ NCI_AB_ACCESS_MODE_RO, /* read only */ ++ NCI_AB_ACCESS_MODE_WO, /* write only */ ++ NCI_AB_ACCESS_MODE_NA, /* No access at all */ ++}; ++ ++/* IRQ-related registers in PSYS */ ++#define IPU6_REG_PSYS_GPDEV_IRQ_EDGE 0x1aa200 ++#define IPU6_REG_PSYS_GPDEV_IRQ_MASK 0x1aa204 ++#define IPU6_REG_PSYS_GPDEV_IRQ_STATUS 0x1aa208 ++#define IPU6_REG_PSYS_GPDEV_IRQ_CLEAR 0x1aa20c ++#define IPU6_REG_PSYS_GPDEV_IRQ_ENABLE 0x1aa210 ++#define IPU6_REG_PSYS_GPDEV_IRQ_LEVEL_NOT_PULSE 0x1aa214 ++/* There are 8 FW interrupts, n = 0..7 */ ++#define IPU6_PSYS_GPDEV_FWIRQ0 5 ++#define IPU6_PSYS_GPDEV_FWIRQ1 6 ++#define IPU6_PSYS_GPDEV_FWIRQ2 7 ++#define IPU6_PSYS_GPDEV_FWIRQ3 8 ++#define IPU6_PSYS_GPDEV_FWIRQ4 9 ++#define IPU6_PSYS_GPDEV_FWIRQ5 10 ++#define IPU6_PSYS_GPDEV_FWIRQ6 11 ++#define IPU6_PSYS_GPDEV_FWIRQ7 12 ++#define IPU6_PSYS_GPDEV_IRQ_FWIRQ(n) BIT(n) ++#define IPU6_REG_PSYS_GPDEV_FWIRQ(n) (4 * (n) + 0x1aa100) ++ ++#endif /* IPU6_PLATFORM_REGS_H */ +diff --git a/drivers/media/pci/intel/ipu6/ipu6.c b/drivers/media/pci/intel/ipu6/ipu6.c +new file mode 100644 +index 000000000000..abd40a783729 +--- /dev/null ++++ b/drivers/media/pci/intel/ipu6/ipu6.c +@@ -0,0 +1,966 @@ ++// SPDX-License-Identifier: GPL-2.0-only ++/* ++ * Copyright (C) 2013 - 2023 Intel Corporation ++ */ ++ ++#include <linux/bitfield.h> ++#include <linux/bits.h> ++#include <linux/dma-mapping.h> ++#include <linux/err.h> ++#include <linux/firmware.h> ++#include <linux/kernel.h> ++#include <linux/interrupt.h> ++#include <linux/io.h> ++#include <linux/list.h> ++#include <linux/module.h> ++#include <linux/pci-ats.h> ++#include <linux/pm_runtime.h> ++#include <linux/property.h> ++#include <linux/scatterlist.h> ++#include <linux/slab.h> ++#include <linux/types.h> ++ ++#include <media/ipu-bridge.h> ++ ++#include "ipu6.h" ++#include "ipu6-bus.h" ++#include "ipu6-buttress.h" ++#include "ipu6-cpd.h" ++#include "ipu6-isys.h" ++#include "ipu6-mmu.h" ++#include "ipu6-platform-buttress-regs.h" ++#include "ipu6-platform-isys-csi2-reg.h" ++#include "ipu6-platform-regs.h" ++ ++#define IPU6_PCI_BAR 0 ++ ++struct ipu6_cell_program { ++ u32 magic_number; ++ ++ u32 blob_offset; ++ u32 blob_size; ++ ++ u32 start[3]; ++ ++ u32 icache_source; ++ u32 icache_target; ++ u32 icache_size; ++ ++ u32 pmem_source; ++ u32 pmem_target; ++ u32 pmem_size; ++ ++ u32 data_source; ++ u32 data_target; ++ u32 data_size; ++ ++ u32 bss_target; ++ u32 bss_size; ++ ++ u32 cell_id; ++ u32 regs_addr; ++ ++ u32 cell_pmem_data_bus_address; ++ u32 cell_dmem_data_bus_address; ++ u32 cell_pmem_control_bus_address; ++ u32 cell_dmem_control_bus_address; ++ ++ u32 next; ++ u32 dummy[2]; ++}; ++ ++static u32 ipu6se_csi_offsets[] = { ++ IPU6_CSI_PORT_A_ADDR_OFFSET, ++ IPU6_CSI_PORT_B_ADDR_OFFSET, ++ IPU6_CSI_PORT_C_ADDR_OFFSET, ++ IPU6_CSI_PORT_D_ADDR_OFFSET ++}; ++ ++/* ++ * IPU6 on TGL support maximum 8 csi2 ports ++ * IPU6SE on JSL and IPU6EP on ADL support maximum 4 csi2 ports ++ * IPU6EP on MTL support maximum 6 csi2 ports ++ */ ++static u32 ipu6_tgl_csi_offsets[] = { ++ IPU6_CSI_PORT_A_ADDR_OFFSET, ++ IPU6_CSI_PORT_B_ADDR_OFFSET, ++ IPU6_CSI_PORT_C_ADDR_OFFSET, ++ IPU6_CSI_PORT_D_ADDR_OFFSET, ++ IPU6_CSI_PORT_E_ADDR_OFFSET, ++ IPU6_CSI_PORT_F_ADDR_OFFSET, ++ IPU6_CSI_PORT_G_ADDR_OFFSET, ++ IPU6_CSI_PORT_H_ADDR_OFFSET ++}; ++ ++static u32 ipu6ep_mtl_csi_offsets[] = { ++ IPU6_CSI_PORT_A_ADDR_OFFSET, ++ IPU6_CSI_PORT_B_ADDR_OFFSET, ++ IPU6_CSI_PORT_C_ADDR_OFFSET, ++ IPU6_CSI_PORT_D_ADDR_OFFSET, ++ IPU6_CSI_PORT_E_ADDR_OFFSET, ++ IPU6_CSI_PORT_F_ADDR_OFFSET ++}; ++ ++static u32 ipu6_csi_offsets[] = { ++ IPU6_CSI_PORT_A_ADDR_OFFSET, ++ IPU6_CSI_PORT_B_ADDR_OFFSET, ++ IPU6_CSI_PORT_C_ADDR_OFFSET, ++ IPU6_CSI_PORT_D_ADDR_OFFSET ++}; ++ ++static struct ipu6_isys_internal_pdata isys_ipdata = { ++ .hw_variant = { ++ .offset = IPU6_UNIFIED_OFFSET, ++ .nr_mmus = 3, ++ .mmu_hw = { ++ { ++ .offset = IPU6_ISYS_IOMMU0_OFFSET, ++ .info_bits = IPU6_INFO_REQUEST_DESTINATION_IOSF, ++ .nr_l1streams = 16, ++ .l1_block_sz = { ++ 3, 8, 2, 2, 2, 2, 2, 2, 1, 1, ++ 1, 1, 1, 1, 1, 1 ++ }, ++ .nr_l2streams = 16, ++ .l2_block_sz = { ++ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, ++ 2, 2, 2, 2, 2, 2 ++ }, ++ .insert_read_before_invalidate = false, ++ .l1_stream_id_reg_offset = ++ IPU6_MMU_L1_STREAM_ID_REG_OFFSET, ++ .l2_stream_id_reg_offset = ++ IPU6_MMU_L2_STREAM_ID_REG_OFFSET, ++ }, ++ { ++ .offset = IPU6_ISYS_IOMMU1_OFFSET, ++ .info_bits = 0, ++ .nr_l1streams = 16, ++ .l1_block_sz = { ++ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, ++ 2, 2, 2, 1, 1, 4 ++ }, ++ .nr_l2streams = 16, ++ .l2_block_sz = { ++ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, ++ 2, 2, 2, 2, 2, 2 ++ }, ++ .insert_read_before_invalidate = false, ++ .l1_stream_id_reg_offset = ++ IPU6_MMU_L1_STREAM_ID_REG_OFFSET, ++ .l2_stream_id_reg_offset = ++ IPU6_MMU_L2_STREAM_ID_REG_OFFSET, ++ }, ++ { ++ .offset = IPU6_ISYS_IOMMUI_OFFSET, ++ .info_bits = 0, ++ .nr_l1streams = 0, ++ .nr_l2streams = 0, ++ .insert_read_before_invalidate = false, ++ }, ++ }, ++ .cdc_fifos = 3, ++ .cdc_fifo_threshold = {6, 8, 2}, ++ .dmem_offset = IPU6_ISYS_DMEM_OFFSET, ++ .spc_offset = IPU6_ISYS_SPC_OFFSET, ++ }, ++ .isys_dma_overshoot = IPU6_ISYS_OVERALLOC_MIN, ++}; ++ ++static struct ipu6_psys_internal_pdata psys_ipdata = { ++ .hw_variant = { ++ .offset = IPU6_UNIFIED_OFFSET, ++ .nr_mmus = 4, ++ .mmu_hw = { ++ { ++ .offset = IPU6_PSYS_IOMMU0_OFFSET, ++ .info_bits = ++ IPU6_INFO_REQUEST_DESTINATION_IOSF, ++ .nr_l1streams = 16, ++ .l1_block_sz = { ++ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, ++ 2, 2, 2, 2, 2, 2 ++ }, ++ .nr_l2streams = 16, ++ .l2_block_sz = { ++ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, ++ 2, 2, 2, 2, 2, 2 ++ }, ++ .insert_read_before_invalidate = false, ++ .l1_stream_id_reg_offset = ++ IPU6_MMU_L1_STREAM_ID_REG_OFFSET, ++ .l2_stream_id_reg_offset = ++ IPU6_MMU_L2_STREAM_ID_REG_OFFSET, ++ }, ++ { ++ .offset = IPU6_PSYS_IOMMU1_OFFSET, ++ .info_bits = 0, ++ .nr_l1streams = 32, ++ .l1_block_sz = { ++ 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, ++ 2, 2, 2, 2, 2, 10, ++ 5, 4, 14, 6, 4, 14, 6, 4, 8, ++ 4, 2, 1, 1, 1, 1, 14 ++ }, ++ .nr_l2streams = 32, ++ .l2_block_sz = { ++ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, ++ 2, 2, 2, 2, 2, 2, ++ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, ++ 2, 2, 2, 2, 2, 2 ++ }, ++ .insert_read_before_invalidate = false, ++ .l1_stream_id_reg_offset = ++ IPU6_MMU_L1_STREAM_ID_REG_OFFSET, ++ .l2_stream_id_reg_offset = ++ IPU6_PSYS_MMU1W_L2_STREAM_ID_REG_OFFSET, ++ }, ++ { ++ .offset = IPU6_PSYS_IOMMU1R_OFFSET, ++ .info_bits = 0, ++ .nr_l1streams = 16, ++ .l1_block_sz = { ++ 1, 4, 4, 4, 4, 16, 8, 4, 32, ++ 16, 16, 2, 2, 2, 1, 12 ++ }, ++ .nr_l2streams = 16, ++ .l2_block_sz = { ++ 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, ++ 2, 2, 2, 2, 2, 2 ++ }, ++ .insert_read_before_invalidate = false, ++ .l1_stream_id_reg_offset = ++ IPU6_MMU_L1_STREAM_ID_REG_OFFSET, ++ .l2_stream_id_reg_offset = ++ IPU6_MMU_L2_STREAM_ID_REG_OFFSET, ++ }, ++ { ++ .offset = IPU6_PSYS_IOMMUI_OFFSET, ++ .info_bits = 0, ++ .nr_l1streams = 0, ++ .nr_l2streams = 0, ++ .insert_read_before_invalidate = false, ++ }, ++ }, ++ .dmem_offset = IPU6_PSYS_DMEM_OFFSET, ++ }, ++}; ++ ++static const struct ipu6_buttress_ctrl isys_buttress_ctrl = { ++ .ratio = IPU6_IS_FREQ_CTL_DEFAULT_RATIO, ++ .qos_floor = IPU6_IS_FREQ_CTL_DEFAULT_QOS_FLOOR_RATIO, ++ .freq_ctl = IPU6_BUTTRESS_REG_IS_FREQ_CTL, ++ .pwr_sts_shift = IPU6_BUTTRESS_PWR_STATE_IS_PWR_SHIFT, ++ .pwr_sts_mask = IPU6_BUTTRESS_PWR_STATE_IS_PWR_MASK, ++ .pwr_sts_on = IPU6_BUTTRESS_PWR_STATE_UP_DONE, ++ .pwr_sts_off = IPU6_BUTTRESS_PWR_STATE_DN_DONE, ++}; ++ ++static const struct ipu6_buttress_ctrl psys_buttress_ctrl = { ++ .ratio = IPU6_PS_FREQ_CTL_DEFAULT_RATIO, ++ .qos_floor = IPU6_PS_FREQ_CTL_DEFAULT_QOS_FLOOR_RATIO, ++ .freq_ctl = IPU6_BUTTRESS_REG_PS_FREQ_CTL, ++ .pwr_sts_shift = IPU6_BUTTRESS_PWR_STATE_PS_PWR_SHIFT, ++ .pwr_sts_mask = IPU6_BUTTRESS_PWR_STATE_PS_PWR_MASK, ++ .pwr_sts_on = IPU6_BUTTRESS_PWR_STATE_UP_DONE, ++ .pwr_sts_off = IPU6_BUTTRESS_PWR_STATE_DN_DONE, ++}; ++ ++static void ++ipu6_pkg_dir_configure_spc(struct ipu6_device *isp, ++ const struct ipu6_hw_variants *hw_variant, ++ int pkg_dir_idx, void __iomem *base, ++ u64 *pkg_dir, dma_addr_t pkg_dir_vied_address) ++{ ++ struct ipu6_cell_program *prog; ++ void __iomem *spc_base; ++ u32 server_fw_addr; ++ dma_addr_t dma_addr; ++ u32 pg_offset; ++ ++ server_fw_addr = lower_32_bits(*(pkg_dir + (pkg_dir_idx + 1) * 2)); ++ if (pkg_dir_idx == IPU6_CPD_PKG_DIR_ISYS_SERVER_IDX) ++ dma_addr = sg_dma_address(isp->isys->fw_sgt.sgl); ++ else ++ dma_addr = sg_dma_address(isp->psys->fw_sgt.sgl); ++ ++ pg_offset = server_fw_addr - dma_addr; ++ prog = (struct ipu6_cell_program *)((u64)isp->cpd_fw->data + pg_offset); ++ spc_base = base + prog->regs_addr; ++ if (spc_base != (base + hw_variant->spc_offset)) ++ dev_warn(&isp->pdev->dev, ++ "SPC reg addr %p not matching value from CPD %p\n", ++ base + hw_variant->spc_offset, spc_base); ++ writel(server_fw_addr + prog->blob_offset + ++ prog->icache_source, spc_base + IPU6_PSYS_REG_SPC_ICACHE_BASE); ++ writel(IPU6_INFO_REQUEST_DESTINATION_IOSF, ++ spc_base + IPU6_REG_PSYS_INFO_SEG_0_CONFIG_ICACHE_MASTER); ++ writel(prog->start[1], spc_base + IPU6_PSYS_REG_SPC_START_PC); ++ writel(pkg_dir_vied_address, base + hw_variant->dmem_offset); ++} ++ ++void ipu6_configure_spc(struct ipu6_device *isp, ++ const struct ipu6_hw_variants *hw_variant, ++ int pkg_dir_idx, void __iomem *base, u64 *pkg_dir, ++ dma_addr_t pkg_dir_dma_addr) ++{ ++ void __iomem *dmem_base = base + hw_variant->dmem_offset; ++ void __iomem *spc_regs_base = base + hw_variant->spc_offset; ++ u32 val; ++ ++ val = readl(spc_regs_base + IPU6_PSYS_REG_SPC_STATUS_CTRL); ++ val |= IPU6_PSYS_SPC_STATUS_CTRL_ICACHE_INVALIDATE; ++ writel(val, spc_regs_base + IPU6_PSYS_REG_SPC_STATUS_CTRL); ++ ++ if (isp->secure_mode) ++ writel(IPU6_PKG_DIR_IMR_OFFSET, dmem_base); ++ else ++ ipu6_pkg_dir_configure_spc(isp, hw_variant, pkg_dir_idx, base, ++ pkg_dir, pkg_dir_dma_addr); ++} ++EXPORT_SYMBOL_NS_GPL(ipu6_configure_spc, INTEL_IPU6); ++ ++static void ipu6_internal_pdata_init(struct ipu6_device *isp) ++{ ++ u8 hw_ver = isp->hw_ver; ++ ++ isys_ipdata.num_parallel_streams = IPU6_ISYS_NUM_STREAMS; ++ isys_ipdata.sram_gran_shift = IPU6_SRAM_GRANULARITY_SHIFT; ++ isys_ipdata.sram_gran_size = IPU6_SRAM_GRANULARITY_SIZE; ++ isys_ipdata.max_sram_size = IPU6_MAX_SRAM_SIZE; ++ isys_ipdata.sensor_type_start = IPU6_FW_ISYS_SENSOR_TYPE_START; ++ isys_ipdata.sensor_type_end = IPU6_FW_ISYS_SENSOR_TYPE_END; ++ isys_ipdata.max_streams = IPU6_ISYS_NUM_STREAMS; ++ isys_ipdata.max_send_queues = IPU6_N_MAX_SEND_QUEUES; ++ isys_ipdata.max_sram_blocks = IPU6_NOF_SRAM_BLOCKS_MAX; ++ isys_ipdata.max_devq_size = IPU6_DEV_SEND_QUEUE_SIZE; ++ isys_ipdata.csi2.nports = ARRAY_SIZE(ipu6_csi_offsets); ++ isys_ipdata.csi2.offsets = ipu6_csi_offsets; ++ isys_ipdata.csi2.irq_mask = IPU6_CSI_RX_ERROR_IRQ_MASK; ++ isys_ipdata.csi2.ctrl0_irq_edge = IPU6_REG_ISYS_CSI_TOP_CTRL0_IRQ_EDGE; ++ isys_ipdata.csi2.ctrl0_irq_clear = ++ IPU6_REG_ISYS_CSI_TOP_CTRL0_IRQ_CLEAR; ++ isys_ipdata.csi2.ctrl0_irq_mask = IPU6_REG_ISYS_CSI_TOP_CTRL0_IRQ_MASK; ++ isys_ipdata.csi2.ctrl0_irq_enable = ++ IPU6_REG_ISYS_CSI_TOP_CTRL0_IRQ_ENABLE; ++ isys_ipdata.csi2.ctrl0_irq_status = ++ IPU6_REG_ISYS_CSI_TOP_CTRL0_IRQ_STATUS; ++ isys_ipdata.csi2.ctrl0_irq_lnp = ++ IPU6_REG_ISYS_CSI_TOP_CTRL0_IRQ_LEVEL_NOT_PULSE; ++ isys_ipdata.enhanced_iwake = is_ipu6ep_mtl(hw_ver) || is_ipu6ep(hw_ver); ++ psys_ipdata.hw_variant.spc_offset = IPU6_PSYS_SPC_OFFSET; ++ isys_ipdata.csi2.fw_access_port_ofs = CSI_REG_HUB_FW_ACCESS_PORT_OFS; ++ ++ if (is_ipu6ep(hw_ver)) { ++ isys_ipdata.ltr = IPU6EP_LTR_VALUE; ++ isys_ipdata.memopen_threshold = IPU6EP_MIN_MEMOPEN_TH; ++ } ++ ++ if (is_ipu6_tgl(hw_ver)) { ++ isys_ipdata.csi2.nports = ARRAY_SIZE(ipu6_tgl_csi_offsets); ++ isys_ipdata.csi2.offsets = ipu6_tgl_csi_offsets; ++ } ++ ++ if (is_ipu6ep_mtl(hw_ver)) { ++ isys_ipdata.csi2.nports = ARRAY_SIZE(ipu6ep_mtl_csi_offsets); ++ isys_ipdata.csi2.offsets = ipu6ep_mtl_csi_offsets; ++ ++ isys_ipdata.csi2.ctrl0_irq_edge = ++ IPU6V6_REG_ISYS_CSI_TOP_CTRL0_IRQ_EDGE; ++ isys_ipdata.csi2.ctrl0_irq_clear = ++ IPU6V6_REG_ISYS_CSI_TOP_CTRL0_IRQ_CLEAR; ++ isys_ipdata.csi2.ctrl0_irq_mask = ++ IPU6V6_REG_ISYS_CSI_TOP_CTRL0_IRQ_MASK; ++ isys_ipdata.csi2.ctrl0_irq_enable = ++ IPU6V6_REG_ISYS_CSI_TOP_CTRL0_IRQ_ENABLE; ++ isys_ipdata.csi2.ctrl0_irq_lnp = ++ IPU6V6_REG_ISYS_CSI_TOP_CTRL0_IRQ_LEVEL_NOT_PULSE; ++ isys_ipdata.csi2.ctrl0_irq_status = ++ IPU6V6_REG_ISYS_CSI_TOP_CTRL0_IRQ_STATUS; ++ isys_ipdata.csi2.fw_access_port_ofs = ++ CSI_REG_HUB_FW_ACCESS_PORT_V6OFS; ++ isys_ipdata.ltr = IPU6EP_MTL_LTR_VALUE; ++ isys_ipdata.memopen_threshold = IPU6EP_MTL_MIN_MEMOPEN_TH; ++ } ++ ++ if (is_ipu6se(hw_ver)) { ++ isys_ipdata.csi2.nports = ARRAY_SIZE(ipu6se_csi_offsets); ++ isys_ipdata.csi2.irq_mask = IPU6SE_CSI_RX_ERROR_IRQ_MASK; ++ isys_ipdata.csi2.offsets = ipu6se_csi_offsets; ++ isys_ipdata.num_parallel_streams = IPU6SE_ISYS_NUM_STREAMS; ++ isys_ipdata.sram_gran_shift = IPU6SE_SRAM_GRANULARITY_SHIFT; ++ isys_ipdata.sram_gran_size = IPU6SE_SRAM_GRANULARITY_SIZE; ++ isys_ipdata.max_sram_size = IPU6SE_MAX_SRAM_SIZE; ++ isys_ipdata.sensor_type_start = ++ IPU6SE_FW_ISYS_SENSOR_TYPE_START; ++ isys_ipdata.sensor_type_end = IPU6SE_FW_ISYS_SENSOR_TYPE_END; ++ isys_ipdata.max_streams = IPU6SE_ISYS_NUM_STREAMS; ++ isys_ipdata.max_send_queues = IPU6SE_N_MAX_SEND_QUEUES; ++ isys_ipdata.max_sram_blocks = IPU6SE_NOF_SRAM_BLOCKS_MAX; ++ isys_ipdata.max_devq_size = IPU6SE_DEV_SEND_QUEUE_SIZE; ++ psys_ipdata.hw_variant.spc_offset = IPU6SE_PSYS_SPC_OFFSET; ++ } ++} ++ ++static int ipu6_isys_check_fwnode_graph(struct fwnode_handle *fwnode) ++{ ++ struct fwnode_handle *endpoint; ++ ++ if (IS_ERR_OR_NULL(fwnode)) ++ return -EINVAL; ++ ++ endpoint = fwnode_graph_get_next_endpoint(fwnode, NULL); ++ if (endpoint) { ++ fwnode_handle_put(endpoint); ++ return 0; ++ } ++ ++ return ipu6_isys_check_fwnode_graph(fwnode->secondary); ++} ++ ++static struct ipu6_bus_device * ++ipu6_isys_init(struct pci_dev *pdev, struct device *parent, ++ struct ipu6_buttress_ctrl *ctrl, void __iomem *base, ++ const struct ipu6_isys_internal_pdata *ipdata) ++{ ++ struct device *dev = &pdev->dev; ++ struct fwnode_handle *fwnode = dev_fwnode(dev); ++ struct ipu6_bus_device *isys_adev; ++ struct ipu6_isys_pdata *pdata; ++ int ret; ++ ++ /* check fwnode at first, fallback into bridge if no fwnode graph */ ++ ret = ipu6_isys_check_fwnode_graph(fwnode); ++ if (ret) { ++ if (fwnode && !IS_ERR_OR_NULL(fwnode->secondary)) { ++ dev_err(dev, ++ "fwnode graph has no endpoints connection\n"); ++ return ERR_PTR(-EINVAL); ++ } ++ ++ ret = ipu_bridge_init(dev, ipu_bridge_parse_ssdb); ++ if (ret) { ++ dev_err_probe(dev, ret, "IPU6 bridge init failed\n"); ++ return ERR_PTR(ret); ++ } ++ } ++ ++ pdata = kzalloc(sizeof(*pdata), GFP_KERNEL); ++ if (!pdata) ++ return ERR_PTR(-ENOMEM); ++ ++ pdata->base = base; ++ pdata->ipdata = ipdata; ++ ++ isys_adev = ipu6_bus_initialize_device(pdev, parent, pdata, ctrl, ++ IPU6_ISYS_NAME); ++ if (IS_ERR(isys_adev)) { ++ dev_err_probe(dev, PTR_ERR(isys_adev), ++ "ipu6_bus_initialize_device isys failed\n"); ++ kfree(pdata); ++ return ERR_CAST(isys_adev); ++ } ++ ++ isys_adev->mmu = ipu6_mmu_init(dev, base, ISYS_MMID, ++ &ipdata->hw_variant); ++ if (IS_ERR(isys_adev->mmu)) { ++ dev_err_probe(dev, PTR_ERR(isys_adev), ++ "ipu6_mmu_init(isys_adev->mmu) failed\n"); ++ put_device(&isys_adev->auxdev.dev); ++ kfree(pdata); ++ return ERR_CAST(isys_adev->mmu); ++ } ++ ++ isys_adev->mmu->dev = &isys_adev->auxdev.dev; ++ ++ ret = ipu6_bus_add_device(isys_adev); ++ if (ret) { ++ kfree(pdata); ++ return ERR_PTR(ret); ++ } ++ ++ return isys_adev; ++} ++ ++static struct ipu6_bus_device * ++ipu6_psys_init(struct pci_dev *pdev, struct device *parent, ++ struct ipu6_buttress_ctrl *ctrl, void __iomem *base, ++ const struct ipu6_psys_internal_pdata *ipdata) ++{ ++ struct ipu6_bus_device *psys_adev; ++ struct ipu6_psys_pdata *pdata; ++ int ret; ++ ++ pdata = kzalloc(sizeof(*pdata), GFP_KERNEL); ++ if (!pdata) ++ return ERR_PTR(-ENOMEM); ++ ++ pdata->base = base; ++ pdata->ipdata = ipdata; ++ ++ psys_adev = ipu6_bus_initialize_device(pdev, parent, pdata, ctrl, ++ IPU6_PSYS_NAME); ++ if (IS_ERR(psys_adev)) { ++ dev_err_probe(&pdev->dev, PTR_ERR(psys_adev), ++ "ipu6_bus_initialize_device psys failed\n"); ++ kfree(pdata); ++ return ERR_CAST(psys_adev); ++ } ++ ++ psys_adev->mmu = ipu6_mmu_init(&pdev->dev, base, PSYS_MMID, ++ &ipdata->hw_variant); ++ if (IS_ERR(psys_adev->mmu)) { ++ dev_err_probe(&pdev->dev, PTR_ERR(psys_adev), ++ "ipu6_mmu_init(psys_adev->mmu) failed\n"); ++ put_device(&psys_adev->auxdev.dev); ++ kfree(pdata); ++ return ERR_CAST(psys_adev->mmu); ++ } ++ ++ psys_adev->mmu->dev = &psys_adev->auxdev.dev; ++ ++ ret = ipu6_bus_add_device(psys_adev); ++ if (ret) { ++ kfree(pdata); ++ return ERR_PTR(ret); ++ } ++ ++ return psys_adev; ++} ++ ++static int ipu6_pci_config_setup(struct pci_dev *dev, u8 hw_ver) ++{ ++ int ret; ++ ++ /* disable IPU6 PCI ATS on mtl ES2 */ ++ if (is_ipu6ep_mtl(hw_ver) && boot_cpu_data.x86_stepping == 0x2 && ++ pci_ats_supported(dev)) ++ pci_disable_ats(dev); ++ ++ /* No PCI msi capability for IPU6EP */ ++ if (is_ipu6ep(hw_ver) || is_ipu6ep_mtl(hw_ver)) { ++ /* likely do nothing as msi not enabled by default */ ++ pci_disable_msi(dev); ++ return 0; ++ } ++ ++ ret = pci_alloc_irq_vectors(dev, 1, 1, PCI_IRQ_MSI); ++ if (ret < 0) ++ return dev_err_probe(&dev->dev, ret, "Request msi failed"); ++ ++ return 0; ++} ++ ++static void ipu6_configure_vc_mechanism(struct ipu6_device *isp) ++{ ++ u32 val = readl(isp->base + BUTTRESS_REG_BTRS_CTRL); ++ ++ if (IPU6_BTRS_ARB_STALL_MODE_VC0 == IPU6_BTRS_ARB_MODE_TYPE_STALL) ++ val |= BUTTRESS_REG_BTRS_CTRL_STALL_MODE_VC0; ++ else ++ val &= ~BUTTRESS_REG_BTRS_CTRL_STALL_MODE_VC0; ++ ++ if (IPU6_BTRS_ARB_STALL_MODE_VC1 == IPU6_BTRS_ARB_MODE_TYPE_STALL) ++ val |= BUTTRESS_REG_BTRS_CTRL_STALL_MODE_VC1; ++ else ++ val &= ~BUTTRESS_REG_BTRS_CTRL_STALL_MODE_VC1; ++ ++ writel(val, isp->base + BUTTRESS_REG_BTRS_CTRL); ++} ++ ++static int request_cpd_fw(const struct firmware **firmware_p, const char *name, ++ struct device *device) ++{ ++ const struct firmware *fw; ++ struct firmware *dst; ++ int ret = 0; ++ ++ ret = request_firmware(&fw, name, device); ++ if (ret) ++ return ret; ++ ++ if (is_vmalloc_addr(fw->data)) { ++ *firmware_p = fw; ++ return 0; ++ } ++ ++ dst = kzalloc(sizeof(*dst), GFP_KERNEL); ++ if (!dst) { ++ ret = -ENOMEM; ++ goto release_firmware; ++ } ++ ++ dst->size = fw->size; ++ dst->data = vmalloc(fw->size); ++ if (!dst->data) { ++ kfree(dst); ++ ret = -ENOMEM; ++ goto release_firmware; ++ } ++ ++ memcpy((void *)dst->data, fw->data, fw->size); ++ *firmware_p = dst; ++ ++release_firmware: ++ release_firmware(fw); ++ ++ return ret; ++} ++ ++static int ipu6_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) ++{ ++ struct ipu6_buttress_ctrl *isys_ctrl = NULL, *psys_ctrl = NULL; ++ struct device *dev = &pdev->dev; ++ void __iomem *isys_base = NULL; ++ void __iomem *psys_base = NULL; ++ struct ipu6_device *isp; ++ phys_addr_t phys; ++ u32 val, version, sku_id; ++ int ret; ++ ++ isp = devm_kzalloc(dev, sizeof(*isp), GFP_KERNEL); ++ if (!isp) ++ return -ENOMEM; ++ ++ isp->pdev = pdev; ++ INIT_LIST_HEAD(&isp->devices); ++ ++ ret = pcim_enable_device(pdev); ++ if (ret) ++ return dev_err_probe(dev, ret, "Enable PCI device failed\n"); ++ ++ phys = pci_resource_start(pdev, IPU6_PCI_BAR); ++ dev_dbg(dev, "IPU6 PCI bar[%u] = %pa\n", IPU6_PCI_BAR, &phys); ++ ++ ret = pcim_iomap_regions(pdev, 1 << IPU6_PCI_BAR, pci_name(pdev)); ++ if (ret) ++ return dev_err_probe(dev, ret, "Failed to I/O mem remappinp\n"); ++ ++ isp->base = pcim_iomap_table(pdev)[IPU6_PCI_BAR]; ++ pci_set_drvdata(pdev, isp); ++ pci_set_master(pdev); ++ ++ isp->cpd_metadata_cmpnt_size = sizeof(struct ipu6_cpd_metadata_cmpnt); ++ switch (id->device) { ++ case PCI_DEVICE_ID_INTEL_IPU6: ++ isp->hw_ver = IPU6_VER_6; ++ isp->cpd_fw_name = IPU6_FIRMWARE_NAME; ++ break; ++ case PCI_DEVICE_ID_INTEL_IPU6SE: ++ isp->hw_ver = IPU6_VER_6SE; ++ isp->cpd_fw_name = IPU6SE_FIRMWARE_NAME; ++ isp->cpd_metadata_cmpnt_size = ++ sizeof(struct ipu6se_cpd_metadata_cmpnt); ++ break; ++ case PCI_DEVICE_ID_INTEL_IPU6EP_ADLP: ++ case PCI_DEVICE_ID_INTEL_IPU6EP_ADLN: ++ case PCI_DEVICE_ID_INTEL_IPU6EP_RPLP: ++ isp->hw_ver = IPU6_VER_6EP; ++ isp->cpd_fw_name = IPU6EP_FIRMWARE_NAME; ++ break; ++ case PCI_DEVICE_ID_INTEL_IPU6EP_MTL: ++ isp->hw_ver = IPU6_VER_6EP_MTL; ++ isp->cpd_fw_name = IPU6EPMTL_FIRMWARE_NAME; ++ break; ++ default: ++ return dev_err_probe(dev, -ENODEV, ++ "Unsupported IPU6 device %x\n", ++ id->device); ++ } ++ ++ ipu6_internal_pdata_init(isp); ++ ++ isys_base = isp->base + isys_ipdata.hw_variant.offset; ++ psys_base = isp->base + psys_ipdata.hw_variant.offset; ++ ++ ret = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(39)); ++ if (ret) ++ return dev_err_probe(dev, ret, "Failed to set DMA mask\n"); ++ ++ ret = dma_set_max_seg_size(dev, UINT_MAX); ++ if (ret) ++ return dev_err_probe(dev, ret, "Failed to set max_seg_size\n"); ++ ++ ret = ipu6_pci_config_setup(pdev, isp->hw_ver); ++ if (ret) ++ return ret; ++ ++ ret = ipu6_buttress_init(isp); ++ if (ret) ++ return ret; ++ ++ ret = request_cpd_fw(&isp->cpd_fw, isp->cpd_fw_name, dev); ++ if (ret) { ++ dev_err_probe(&isp->pdev->dev, ret, ++ "Requesting signed firmware %s failed\n", ++ isp->cpd_fw_name); ++ goto buttress_exit; ++ } ++ ++ ret = ipu6_cpd_validate_cpd_file(isp, isp->cpd_fw->data, ++ isp->cpd_fw->size); ++ if (ret) { ++ dev_err_probe(&isp->pdev->dev, ret, ++ "Failed to validate cpd\n"); ++ goto out_ipu6_bus_del_devices; ++ } ++ ++ isys_ctrl = devm_kmemdup(dev, &isys_buttress_ctrl, ++ sizeof(isys_buttress_ctrl), GFP_KERNEL); ++ if (!isys_ctrl) { ++ ret = -ENOMEM; ++ goto out_ipu6_bus_del_devices; ++ } ++ ++ isp->isys = ipu6_isys_init(pdev, dev, isys_ctrl, isys_base, ++ &isys_ipdata); ++ if (IS_ERR(isp->isys)) { ++ ret = PTR_ERR(isp->isys); ++ goto out_ipu6_bus_del_devices; ++ } ++ ++ psys_ctrl = devm_kmemdup(dev, &psys_buttress_ctrl, ++ sizeof(psys_buttress_ctrl), GFP_KERNEL); ++ if (!psys_ctrl) { ++ ret = -ENOMEM; ++ goto out_ipu6_bus_del_devices; ++ } ++ ++ isp->psys = ipu6_psys_init(pdev, &isp->isys->auxdev.dev, psys_ctrl, ++ psys_base, &psys_ipdata); ++ if (IS_ERR(isp->psys)) { ++ ret = PTR_ERR(isp->psys); ++ goto out_ipu6_bus_del_devices; ++ } ++ ++ ret = pm_runtime_resume_and_get(&isp->psys->auxdev.dev); ++ if (ret < 0) ++ goto out_ipu6_bus_del_devices; ++ ++ ret = ipu6_mmu_hw_init(isp->psys->mmu); ++ if (ret) { ++ dev_err_probe(&isp->pdev->dev, ret, ++ "Failed to set MMU hardware\n"); ++ goto out_ipu6_bus_del_devices; ++ } ++ ++ ret = ipu6_buttress_map_fw_image(isp->psys, isp->cpd_fw, ++ &isp->psys->fw_sgt); ++ if (ret) { ++ dev_err_probe(&isp->pdev->dev, ret, "failed to map fw image\n"); ++ goto out_ipu6_bus_del_devices; ++ } ++ ++ ret = ipu6_cpd_create_pkg_dir(isp->psys, isp->cpd_fw->data); ++ if (ret) { ++ dev_err_probe(&isp->pdev->dev, ret, ++ "failed to create pkg dir\n"); ++ goto out_ipu6_bus_del_devices; ++ } ++ ++ ret = devm_request_threaded_irq(dev, pdev->irq, ipu6_buttress_isr, ++ ipu6_buttress_isr_threaded, ++ IRQF_SHARED, IPU6_NAME, isp); ++ if (ret) { ++ dev_err_probe(dev, ret, "Requesting irq failed\n"); ++ goto out_ipu6_bus_del_devices; ++ } ++ ++ ret = ipu6_buttress_authenticate(isp); ++ if (ret) { ++ dev_err_probe(&isp->pdev->dev, ret, ++ "FW authentication failed\n"); ++ goto out_free_irq; ++ } ++ ++ ipu6_mmu_hw_cleanup(isp->psys->mmu); ++ pm_runtime_put(&isp->psys->auxdev.dev); ++ ++ /* Configure the arbitration mechanisms for VC requests */ ++ ipu6_configure_vc_mechanism(isp); ++ ++ val = readl(isp->base + BUTTRESS_REG_SKU); ++ sku_id = FIELD_GET(GENMASK(6, 4), val); ++ version = FIELD_GET(GENMASK(3, 0), val); ++ dev_info(dev, "IPU%u-v%u[%x] hardware version %d\n", version, sku_id, ++ pdev->device, isp->hw_ver); ++ ++ pm_runtime_put_noidle(dev); ++ pm_runtime_allow(dev); ++ ++ isp->bus_ready_to_probe = true; ++ ++ return 0; ++ ++out_free_irq: ++ devm_free_irq(dev, pdev->irq, isp); ++out_ipu6_bus_del_devices: ++ if (isp->psys) { ++ ipu6_cpd_free_pkg_dir(isp->psys); ++ ipu6_buttress_unmap_fw_image(isp->psys, &isp->psys->fw_sgt); ++ } ++ if (!IS_ERR_OR_NULL(isp->psys) && !IS_ERR_OR_NULL(isp->psys->mmu)) ++ ipu6_mmu_cleanup(isp->psys->mmu); ++ if (!IS_ERR_OR_NULL(isp->isys) && !IS_ERR_OR_NULL(isp->isys->mmu)) ++ ipu6_mmu_cleanup(isp->isys->mmu); ++ ipu6_bus_del_devices(pdev); ++ release_firmware(isp->cpd_fw); ++buttress_exit: ++ ipu6_buttress_exit(isp); ++ ++ return ret; ++} ++ ++static void ipu6_pci_remove(struct pci_dev *pdev) ++{ ++ struct ipu6_device *isp = pci_get_drvdata(pdev); ++ struct ipu6_mmu *isys_mmu = isp->isys->mmu; ++ struct ipu6_mmu *psys_mmu = isp->psys->mmu; ++ ++ devm_free_irq(&pdev->dev, pdev->irq, isp); ++ ipu6_cpd_free_pkg_dir(isp->psys); ++ ++ ipu6_buttress_unmap_fw_image(isp->psys, &isp->psys->fw_sgt); ++ ipu6_buttress_exit(isp); ++ ++ ipu6_bus_del_devices(pdev); ++ ++ pm_runtime_forbid(&pdev->dev); ++ pm_runtime_get_noresume(&pdev->dev); ++ ++ pci_release_regions(pdev); ++ pci_disable_device(pdev); ++ ++ release_firmware(isp->cpd_fw); ++ ++ ipu6_mmu_cleanup(psys_mmu); ++ ipu6_mmu_cleanup(isys_mmu); ++} ++ ++static void ipu6_pci_reset_prepare(struct pci_dev *pdev) ++{ ++ struct ipu6_device *isp = pci_get_drvdata(pdev); ++ ++ pm_runtime_forbid(&isp->pdev->dev); ++} ++ ++static void ipu6_pci_reset_done(struct pci_dev *pdev) ++{ ++ struct ipu6_device *isp = pci_get_drvdata(pdev); ++ ++ ipu6_buttress_restore(isp); ++ if (isp->secure_mode) ++ ipu6_buttress_reset_authentication(isp); ++ ++ isp->need_ipc_reset = true; ++ pm_runtime_allow(&isp->pdev->dev); ++} ++ ++/* ++ * PCI base driver code requires driver to provide these to enable ++ * PCI device level PM state transitions (D0<->D3) ++ */ ++static int ipu6_suspend(struct device *dev) ++{ ++ return 0; ++} ++ ++static int ipu6_resume(struct device *dev) ++{ ++ struct pci_dev *pdev = to_pci_dev(dev); ++ struct ipu6_device *isp = pci_get_drvdata(pdev); ++ struct ipu6_buttress *b = &isp->buttress; ++ int ret; ++ ++ /* Configure the arbitration mechanisms for VC requests */ ++ ipu6_configure_vc_mechanism(isp); ++ ++ isp->secure_mode = ipu6_buttress_get_secure_mode(isp); ++ dev_info(dev, "IPU6 in %s mode\n", ++ isp->secure_mode ? "secure" : "non-secure"); ++ ++ ipu6_buttress_restore(isp); ++ ++ ret = ipu6_buttress_ipc_reset(isp, &b->cse); ++ if (ret) ++ dev_err(&isp->pdev->dev, "IPC reset protocol failed!\n"); ++ ++ ret = pm_runtime_resume_and_get(&isp->psys->auxdev.dev); ++ if (ret < 0) { ++ dev_err(&isp->psys->auxdev.dev, "Failed to get runtime PM\n"); ++ return 0; ++ } ++ ++ ret = ipu6_buttress_authenticate(isp); ++ if (ret) ++ dev_err(&isp->pdev->dev, "FW authentication failed(%d)\n", ret); ++ ++ pm_runtime_put(&isp->psys->auxdev.dev); ++ ++ return 0; ++} ++ ++static int ipu6_runtime_resume(struct device *dev) ++{ ++ struct pci_dev *pdev = to_pci_dev(dev); ++ struct ipu6_device *isp = pci_get_drvdata(pdev); ++ int ret; ++ ++ ipu6_configure_vc_mechanism(isp); ++ ipu6_buttress_restore(isp); ++ ++ if (isp->need_ipc_reset) { ++ struct ipu6_buttress *b = &isp->buttress; ++ ++ isp->need_ipc_reset = false; ++ ret = ipu6_buttress_ipc_reset(isp, &b->cse); ++ if (ret) ++ dev_err(&isp->pdev->dev, "IPC reset protocol failed\n"); ++ } ++ ++ return 0; ++} ++ ++static const struct dev_pm_ops ipu6_pm_ops = { ++ SET_SYSTEM_SLEEP_PM_OPS(&ipu6_suspend, &ipu6_resume) ++ SET_RUNTIME_PM_OPS(&ipu6_suspend, &ipu6_runtime_resume, NULL) ++}; ++ ++static const struct pci_device_id ipu6_pci_tbl[] = { ++ { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_IPU6) }, ++ { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_IPU6SE) }, ++ { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_IPU6EP_ADLP) }, ++ { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_IPU6EP_ADLN) }, ++ { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_IPU6EP_RPLP) }, ++ { PCI_VDEVICE(INTEL, PCI_DEVICE_ID_INTEL_IPU6EP_MTL) }, ++ { } ++}; ++MODULE_DEVICE_TABLE(pci, ipu6_pci_tbl); ++ ++static const struct pci_error_handlers pci_err_handlers = { ++ .reset_prepare = ipu6_pci_reset_prepare, ++ .reset_done = ipu6_pci_reset_done, ++}; ++ ++static struct pci_driver ipu6_pci_driver = { ++ .name = IPU6_NAME, ++ .id_table = ipu6_pci_tbl, ++ .probe = ipu6_pci_probe, ++ .remove = ipu6_pci_remove, ++ .driver = { ++ .pm = pm_ptr(&ipu6_pm_ops), ++ }, ++ .err_handler = &pci_err_handlers, ++}; ++ ++module_pci_driver(ipu6_pci_driver); ++ ++MODULE_IMPORT_NS(INTEL_IPU_BRIDGE); ++MODULE_AUTHOR("Sakari Ailus <sakari.ailus@linux.intel.com>"); ++MODULE_AUTHOR("Tianshu Qiu <tian.shu.qiu@intel.com>"); ++MODULE_AUTHOR("Bingbu Cao <bingbu.cao@intel.com>"); ++MODULE_AUTHOR("Qingwu Zhang <qingwu.zhang@intel.com>"); ++MODULE_AUTHOR("Yunliang Ding <yunliang.ding@intel.com>"); ++MODULE_AUTHOR("Hongju Wang <hongju.wang@intel.com>"); ++MODULE_LICENSE("GPL"); ++MODULE_DESCRIPTION("Intel IPU6 PCI driver"); +diff --git a/drivers/media/pci/intel/ipu6/ipu6.h b/drivers/media/pci/intel/ipu6/ipu6.h +new file mode 100644 +index 000000000000..04e7e7e61ca5 +--- /dev/null ++++ b/drivers/media/pci/intel/ipu6/ipu6.h +@@ -0,0 +1,356 @@ ++/* SPDX-License-Identifier: GPL-2.0-only */ ++/* Copyright (C) 2013 - 2023 Intel Corporation */ ++ ++#ifndef IPU6_H ++#define IPU6_H ++ ++#include <linux/list.h> ++#include <linux/pci.h> ++#include <linux/types.h> ++ ++#include "ipu6-buttress.h" ++ ++struct firmware; ++struct pci_dev; ++struct ipu6_bus_device; ++ ++#define PCI_DEVICE_ID_INTEL_IPU6 0x9a19 ++#define PCI_DEVICE_ID_INTEL_IPU6SE 0x4e19 ++#define PCI_DEVICE_ID_INTEL_IPU6EP_ADLP 0x465d ++#define PCI_DEVICE_ID_INTEL_IPU6EP_ADLN 0x462e ++#define PCI_DEVICE_ID_INTEL_IPU6EP_RPLP 0xa75d ++#define PCI_DEVICE_ID_INTEL_IPU6EP_MTL 0x7d19 ++ ++#define IPU6_NAME "intel-ipu6" ++#define IPU6_MEDIA_DEV_MODEL_NAME "ipu6" ++ ++#define IPU6SE_FIRMWARE_NAME "intel/ipu6se_fw.bin" ++#define IPU6EP_FIRMWARE_NAME "intel/ipu6ep_fw.bin" ++#define IPU6_FIRMWARE_NAME "intel/ipu6_fw.bin" ++#define IPU6EPMTL_FIRMWARE_NAME "intel/ipu6epmtl_fw.bin" ++ ++enum ipu6_version { ++ IPU6_VER_INVALID = 0, ++ IPU6_VER_6 = 1, ++ IPU6_VER_6SE = 3, ++ IPU6_VER_6EP = 5, ++ IPU6_VER_6EP_MTL = 6, ++}; ++ ++/* ++ * IPU6 - TGL ++ * IPU6SE - JSL ++ * IPU6EP - ADL/RPL ++ * IPU6EP_MTL - MTL ++ */ ++static inline bool is_ipu6se(u8 hw_ver) ++{ ++ return hw_ver == IPU6_VER_6SE; ++} ++ ++static inline bool is_ipu6ep(u8 hw_ver) ++{ ++ return hw_ver == IPU6_VER_6EP; ++} ++ ++static inline bool is_ipu6ep_mtl(u8 hw_ver) ++{ ++ return hw_ver == IPU6_VER_6EP_MTL; ++} ++ ++static inline bool is_ipu6_tgl(u8 hw_ver) ++{ ++ return hw_ver == IPU6_VER_6; ++} ++ ++/* ++ * ISYS DMA can overshoot. For higher resolutions over allocation is one line ++ * but it must be at minimum 1024 bytes. Value could be different in ++ * different versions / generations thus provide it via platform data. ++ */ ++#define IPU6_ISYS_OVERALLOC_MIN 1024 ++ ++/* Physical pages in GDA is 128, page size is 2K for IPU6, 1K for others */ ++#define IPU6_DEVICE_GDA_NR_PAGES 128 ++ ++/* Virtualization factor to calculate the available virtual pages */ ++#define IPU6_DEVICE_GDA_VIRT_FACTOR 32 ++ ++#define NR_OF_MMU_RESOURCES 2 ++ ++struct ipu6_device { ++ struct pci_dev *pdev; ++ struct list_head devices; ++ struct ipu6_bus_device *isys; ++ struct ipu6_bus_device *psys; ++ struct ipu6_buttress buttress; ++ ++ const struct firmware *cpd_fw; ++ const char *cpd_fw_name; ++ u32 cpd_metadata_cmpnt_size; ++ ++ void __iomem *base; ++ bool need_ipc_reset; ++ bool secure_mode; ++ u8 hw_ver; ++ bool bus_ready_to_probe; ++}; ++ ++#define IPU6_ISYS_NAME "isys" ++#define IPU6_PSYS_NAME "psys" ++ ++#define IPU6_MMU_MAX_DEVICES 4 ++#define IPU6_MMU_ADDR_BITS 32 ++/* The firmware is accessible within the first 2 GiB only in non-secure mode. */ ++#define IPU6_MMU_ADDR_BITS_NON_SECURE 31 ++ ++#define IPU6_MMU_MAX_TLB_L1_STREAMS 32 ++#define IPU6_MMU_MAX_TLB_L2_STREAMS 32 ++#define IPU6_MAX_LI_BLOCK_ADDR 128 ++#define IPU6_MAX_L2_BLOCK_ADDR 64 ++ ++#define IPU6_ISYS_MAX_CSI2_LEGACY_PORTS 4 ++#define IPU6_ISYS_MAX_CSI2_COMBO_PORTS 2 ++ ++#define IPU6_MAX_FRAME_COUNTER 0xff ++ ++#define IPU6SE_ISYS_NUM_STREAMS IPU6SE_NONSECURE_STREAM_ID_MAX ++#define IPU6_ISYS_NUM_STREAMS IPU6_NONSECURE_STREAM_ID_MAX ++ ++/* ++ * To maximize the IOSF utlization, IPU6 need to send requests in bursts. ++ * At the DMA interface with the buttress, there are CDC FIFOs with burst ++ * collection capability. CDC FIFO burst collectors have a configurable ++ * threshold and is configured based on the outcome of performance measurements. ++ * ++ * isys has 3 ports with IOSF interface for VC0, VC1 and VC2 ++ * psys has 4 ports with IOSF interface for VC0, VC1w, VC1r and VC2 ++ * ++ * Threshold values are pre-defined and are arrived at after performance ++ * evaluations on a type of IPU6 ++ */ ++#define IPU6_MAX_VC_IOSF_PORTS 4 ++ ++/* ++ * IPU6 must configure correct arbitration mechanism related to the IOSF VC ++ * requests. There are two options per VC0 and VC1 - > 0 means rearbitrate on ++ * stall and 1 means stall until the request is completed. ++ */ ++#define IPU6_BTRS_ARB_MODE_TYPE_REARB 0 ++#define IPU6_BTRS_ARB_MODE_TYPE_STALL 1 ++ ++/* Currently chosen arbitration mechanism for VC0 */ ++#define IPU6_BTRS_ARB_STALL_MODE_VC0 \ ++ IPU6_BTRS_ARB_MODE_TYPE_REARB ++ ++/* Currently chosen arbitration mechanism for VC1 */ ++#define IPU6_BTRS_ARB_STALL_MODE_VC1 \ ++ IPU6_BTRS_ARB_MODE_TYPE_REARB ++ ++/* ++ * MMU Invalidation HW bug workaround by ZLW mechanism ++ * ++ * Old IPU6 MMUV2 has a bug in the invalidation mechanism which might result in ++ * wrong translation or replication of the translation. This will cause data ++ * corruption. So we cannot directly use the MMU V2 invalidation registers ++ * to invalidate the MMU. Instead, whenever an invalidate is called, we need to ++ * clear the TLB by evicting all the valid translations by filling it with trash ++ * buffer (which is guaranteed not to be used by any other processes). ZLW is ++ * used to fill the L1 and L2 caches with the trash buffer translations. ZLW ++ * or Zero length write, is pre-fetch mechanism to pre-fetch the pages in ++ * advance to the L1 and L2 caches without triggering any memory operations. ++ * ++ * In MMU V2, L1 -> 16 streams and 64 blocks, maximum 16 blocks per stream ++ * One L1 block has 16 entries, hence points to 16 * 4K pages ++ * L2 -> 16 streams and 32 blocks. 2 blocks per streams ++ * One L2 block maps to 1024 L1 entries, hence points to 4MB address range ++ * 2 blocks per L2 stream means, 1 stream points to 8MB range ++ * ++ * As we need to clear the caches and 8MB being the biggest cache size, we need ++ * to have trash buffer which points to 8MB address range. As these trash ++ * buffers are not used for any memory transactions, we need only the least ++ * amount of physical memory. So we reserve 8MB IOVA address range but only ++ * one page is reserved from physical memory. Each of this 8MB IOVA address ++ * range is then mapped to the same physical memory page. ++ */ ++/* One L2 entry maps 1024 L1 entries and one L1 entry per page */ ++#define IPU6_MMUV2_L2_RANGE (1024 * PAGE_SIZE) ++/* Max L2 blocks per stream */ ++#define IPU6_MMUV2_MAX_L2_BLOCKS 2 ++/* Max L1 blocks per stream */ ++#define IPU6_MMUV2_MAX_L1_BLOCKS 16 ++#define IPU6_MMUV2_TRASH_RANGE (IPU6_MMUV2_L2_RANGE * IPU6_MMUV2_MAX_L2_BLOCKS) ++/* Entries per L1 block */ ++#define MMUV2_ENTRIES_PER_L1_BLOCK 16 ++#define MMUV2_TRASH_L1_BLOCK_OFFSET (MMUV2_ENTRIES_PER_L1_BLOCK * PAGE_SIZE) ++#define MMUV2_TRASH_L2_BLOCK_OFFSET IPU6_MMUV2_L2_RANGE ++ ++/* ++ * In some of the IPU6 MMUs, there is provision to configure L1 and L2 page ++ * table caches. Both these L1 and L2 caches are divided into multiple sections ++ * called streams. There is maximum 16 streams for both caches. Each of these ++ * sections are subdivided into multiple blocks. When nr_l1streams = 0 and ++ * nr_l2streams = 0, means the MMU is of type MMU_V1 and do not support ++ * L1/L2 page table caches. ++ * ++ * L1 stream per block sizes are configurable and varies per usecase. ++ * L2 has constant block sizes - 2 blocks per stream. ++ * ++ * MMU1 support pre-fetching of the pages to have less cache lookup misses. To ++ * enable the pre-fetching, MMU1 AT (Address Translator) device registers ++ * need to be configured. ++ * ++ * There are four types of memory accesses which requires ZLW configuration. ++ * ZLW(Zero Length Write) is a mechanism to enable VT-d pre-fetching on IOMMU. ++ * ++ * 1. Sequential Access or 1D mode ++ * Set ZLW_EN -> 1 ++ * set ZLW_PAGE_CROSS_1D -> 1 ++ * Set ZLW_N to "N" pages so that ZLW will be inserte N pages ahead where ++ * N is pre-defined and hardcoded in the platform data ++ * Set ZLW_2D -> 0 ++ * ++ * 2. ZLW 2D mode ++ * Set ZLW_EN -> 1 ++ * set ZLW_PAGE_CROSS_1D -> 1, ++ * Set ZLW_N -> 0 ++ * Set ZLW_2D -> 1 ++ * ++ * 3. ZLW Enable (no 1D or 2D mode) ++ * Set ZLW_EN -> 1 ++ * set ZLW_PAGE_CROSS_1D -> 0, ++ * Set ZLW_N -> 0 ++ * Set ZLW_2D -> 0 ++ * ++ * 4. ZLW disable ++ * Set ZLW_EN -> 0 ++ * set ZLW_PAGE_CROSS_1D -> 0, ++ * Set ZLW_N -> 0 ++ * Set ZLW_2D -> 0 ++ * ++ * To configure the ZLW for the above memory access, four registers are ++ * available. Hence to track these four settings, we have the following entries ++ * in the struct ipu6_mmu_hw. Each of these entries are per stream and ++ * available only for the L1 streams. ++ * ++ * a. l1_zlw_en -> To track zlw enabled per stream (ZLW_EN) ++ * b. l1_zlw_1d_mode -> Track 1D mode per stream. ZLW inserted at page boundary ++ * c. l1_ins_zlw_ahead_pages -> to track how advance the ZLW need to be inserted ++ * Insert ZLW request N pages ahead address. ++ * d. l1_zlw_2d_mode -> To track 2D mode per stream (ZLW_2D) ++ * ++ * ++ * Currently L1/L2 streams, blocks, AT ZLW configurations etc. are pre-defined ++ * as per the usecase specific calculations. Any change to this pre-defined ++ * table has to happen in sync with IPU6 FW. ++ */ ++struct ipu6_mmu_hw { ++ union { ++ unsigned long offset; ++ void __iomem *base; ++ }; ++ u32 info_bits; ++ u8 nr_l1streams; ++ /* ++ * L1 has variable blocks per stream - total of 64 blocks and maximum of ++ * 16 blocks per stream. Configurable by using the block start address ++ * per stream. Block start address is calculated from the block size ++ */ ++ u8 l1_block_sz[IPU6_MMU_MAX_TLB_L1_STREAMS]; ++ /* Is ZLW is enabled in each stream */ ++ bool l1_zlw_en[IPU6_MMU_MAX_TLB_L1_STREAMS]; ++ bool l1_zlw_1d_mode[IPU6_MMU_MAX_TLB_L1_STREAMS]; ++ u8 l1_ins_zlw_ahead_pages[IPU6_MMU_MAX_TLB_L1_STREAMS]; ++ bool l1_zlw_2d_mode[IPU6_MMU_MAX_TLB_L1_STREAMS]; ++ ++ u32 l1_stream_id_reg_offset; ++ u32 l2_stream_id_reg_offset; ++ ++ u8 nr_l2streams; ++ /* ++ * L2 has fixed 2 blocks per stream. Block address is calculated ++ * from the block size ++ */ ++ u8 l2_block_sz[IPU6_MMU_MAX_TLB_L2_STREAMS]; ++ /* flag to track if WA is needed for successive invalidate HW bug */ ++ bool insert_read_before_invalidate; ++}; ++ ++struct ipu6_mmu_pdata { ++ u32 nr_mmus; ++ struct ipu6_mmu_hw mmu_hw[IPU6_MMU_MAX_DEVICES]; ++ int mmid; ++}; ++ ++struct ipu6_isys_csi2_pdata { ++ void __iomem *base; ++}; ++ ++struct ipu6_isys_internal_csi2_pdata { ++ u32 nports; ++ u32 irq_mask; ++ u32 *offsets; ++ u32 ctrl0_irq_edge; ++ u32 ctrl0_irq_clear; ++ u32 ctrl0_irq_mask; ++ u32 ctrl0_irq_enable; ++ u32 ctrl0_irq_lnp; ++ u32 ctrl0_irq_status; ++ u32 fw_access_port_ofs; ++}; ++ ++struct ipu6_isys_internal_tpg_pdata { ++ u32 ntpgs; ++ u32 *offsets; ++ u32 *sels; ++}; ++ ++struct ipu6_hw_variants { ++ unsigned long offset; ++ u32 nr_mmus; ++ struct ipu6_mmu_hw mmu_hw[IPU6_MMU_MAX_DEVICES]; ++ u8 cdc_fifos; ++ u8 cdc_fifo_threshold[IPU6_MAX_VC_IOSF_PORTS]; ++ u32 dmem_offset; ++ u32 spc_offset; ++}; ++ ++struct ipu6_isys_internal_pdata { ++ struct ipu6_isys_internal_csi2_pdata csi2; ++ struct ipu6_hw_variants hw_variant; ++ u32 num_parallel_streams; ++ u32 isys_dma_overshoot; ++ u32 sram_gran_shift; ++ u32 sram_gran_size; ++ u32 max_sram_size; ++ u32 max_streams; ++ u32 max_send_queues; ++ u32 max_sram_blocks; ++ u32 max_devq_size; ++ u32 sensor_type_start; ++ u32 sensor_type_end; ++ u32 ltr; ++ u32 memopen_threshold; ++ bool enhanced_iwake; ++}; ++ ++struct ipu6_isys_pdata { ++ void __iomem *base; ++ const struct ipu6_isys_internal_pdata *ipdata; ++}; ++ ++struct ipu6_psys_internal_pdata { ++ struct ipu6_hw_variants hw_variant; ++}; ++ ++struct ipu6_psys_pdata { ++ void __iomem *base; ++ const struct ipu6_psys_internal_pdata *ipdata; ++}; ++ ++int ipu6_fw_authenticate(void *data, u64 val); ++void ipu6_configure_spc(struct ipu6_device *isp, ++ const struct ipu6_hw_variants *hw_variant, ++ int pkg_dir_idx, void __iomem *base, u64 *pkg_dir, ++ dma_addr_t pkg_dir_dma_addr); ++#endif /* IPU6_H */ +-- +2.43.0 + +From 79ff4f8a1383c679fc4bbee832e51e6369cbc21c Mon Sep 17 00:00:00 2001 +From: Bingbu Cao <bingbu.cao@intel.com> +Date: Thu, 11 Jan 2024 14:55:16 +0800 +Subject: [PATCH 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 <bingbu.cao@intel.com> +Link: https://lore.kernel.org/r/20240111065531.2418836-3-bingbu.cao@intel.com +Signed-off-by: Hans de Goede <hdegoede@redhat.com> +--- + drivers/media/pci/intel/ipu6/ipu6-bus.c | 165 ++++++++++++++++++++++++ + drivers/media/pci/intel/ipu6/ipu6-bus.h | 58 +++++++++ + 2 files changed, 223 insertions(+) + create mode 100644 drivers/media/pci/intel/ipu6/ipu6-bus.c + create mode 100644 drivers/media/pci/intel/ipu6/ipu6-bus.h + +diff --git a/drivers/media/pci/intel/ipu6/ipu6-bus.c b/drivers/media/pci/intel/ipu6/ipu6-bus.c +new file mode 100644 +index 000000000000..e81b9a6518a1 +--- /dev/null ++++ b/drivers/media/pci/intel/ipu6/ipu6-bus.c +@@ -0,0 +1,165 @@ ++// SPDX-License-Identifier: GPL-2.0-only ++/* ++ * Copyright (C) 2013 - 2023 Intel Corporation ++ */ ++ ++#include <linux/auxiliary_bus.h> ++#include <linux/device.h> ++#include <linux/dma-mapping.h> ++#include <linux/err.h> ++#include <linux/list.h> ++#include <linux/mutex.h> ++#include <linux/pci.h> ++#include <linux/pm_domain.h> ++#include <linux/pm_runtime.h> ++#include <linux/slab.h> ++ ++#include "ipu6.h" ++#include "ipu6-bus.h" ++#include "ipu6-buttress.h" ++#include "ipu6-dma.h" ++ ++static int bus_pm_runtime_suspend(struct device *dev) ++{ ++ struct ipu6_bus_device *adev = to_ipu6_bus_device(dev); ++ int ret; ++ ++ ret = pm_generic_runtime_suspend(dev); ++ if (ret) ++ return ret; ++ ++ ret = ipu6_buttress_power(dev, adev->ctrl, false); ++ if (!ret) ++ return 0; ++ ++ dev_err(dev, "power down failed!\n"); ++ ++ /* Powering down failed, attempt to resume device now */ ++ ret = pm_generic_runtime_resume(dev); ++ if (!ret) ++ return -EBUSY; ++ ++ return -EIO; ++} ++ ++static int bus_pm_runtime_resume(struct device *dev) ++{ ++ struct ipu6_bus_device *adev = to_ipu6_bus_device(dev); ++ int ret; ++ ++ ret = ipu6_buttress_power(dev, adev->ctrl, true); ++ if (ret) ++ return ret; ++ ++ ret = pm_generic_runtime_resume(dev); ++ if (ret) ++ goto out_err; ++ ++ return 0; ++ ++out_err: ++ ipu6_buttress_power(dev, adev->ctrl, false); ++ ++ return -EBUSY; ++} ++ ++static struct dev_pm_domain ipu6_bus_pm_domain = { ++ .ops = { ++ .runtime_suspend = bus_pm_runtime_suspend, ++ .runtime_resume = bus_pm_runtime_resume, ++ }, ++}; ++ ++static DEFINE_MUTEX(ipu6_bus_mutex); ++ ++static void ipu6_bus_release(struct device *dev) ++{ ++ struct ipu6_bus_device *adev = to_ipu6_bus_device(dev); ++ ++ kfree(adev->pdata); ++ kfree(adev); ++} ++ ++struct ipu6_bus_device * ++ipu6_bus_initialize_device(struct pci_dev *pdev, struct device *parent, ++ void *pdata, struct ipu6_buttress_ctrl *ctrl, ++ char *name) ++{ ++ struct auxiliary_device *auxdev; ++ struct ipu6_bus_device *adev; ++ struct ipu6_device *isp = pci_get_drvdata(pdev); ++ int ret; ++ ++ adev = kzalloc(sizeof(*adev), GFP_KERNEL); ++ if (!adev) ++ return ERR_PTR(-ENOMEM); ++ ++ adev->dma_mask = DMA_BIT_MASK(isp->secure_mode ? IPU6_MMU_ADDR_BITS : ++ IPU6_MMU_ADDR_BITS_NON_SECURE); ++ adev->isp = isp; ++ adev->ctrl = ctrl; ++ adev->pdata = pdata; ++ auxdev = &adev->auxdev; ++ auxdev->name = name; ++ auxdev->id = (pci_domain_nr(pdev->bus) << 16) | ++ PCI_DEVID(pdev->bus->number, pdev->devfn); ++ ++ auxdev->dev.parent = parent; ++ auxdev->dev.release = ipu6_bus_release; ++ auxdev->dev.dma_ops = &ipu6_dma_ops; ++ auxdev->dev.dma_mask = &adev->dma_mask; ++ auxdev->dev.dma_parms = pdev->dev.dma_parms; ++ auxdev->dev.coherent_dma_mask = adev->dma_mask; ++ ++ ret = auxiliary_device_init(auxdev); ++ if (ret < 0) { ++ dev_err(&isp->pdev->dev, "auxiliary device init failed (%d)\n", ++ ret); ++ kfree(adev); ++ return ERR_PTR(ret); ++ } ++ ++ dev_pm_domain_set(&auxdev->dev, &ipu6_bus_pm_domain); ++ ++ pm_runtime_forbid(&adev->auxdev.dev); ++ pm_runtime_enable(&adev->auxdev.dev); ++ ++ return adev; ++} ++ ++int ipu6_bus_add_device(struct ipu6_bus_device *adev) ++{ ++ struct auxiliary_device *auxdev = &adev->auxdev; ++ int ret; ++ ++ ret = auxiliary_device_add(auxdev); ++ if (ret) { ++ auxiliary_device_uninit(auxdev); ++ return ret; ++ } ++ ++ mutex_lock(&ipu6_bus_mutex); ++ list_add(&adev->list, &adev->isp->devices); ++ mutex_unlock(&ipu6_bus_mutex); ++ ++ pm_runtime_allow(&auxdev->dev); ++ ++ return 0; ++} ++ ++void ipu6_bus_del_devices(struct pci_dev *pdev) ++{ ++ struct ipu6_device *isp = pci_get_drvdata(pdev); ++ struct ipu6_bus_device *adev, *save; ++ ++ mutex_lock(&ipu6_bus_mutex); ++ ++ list_for_each_entry_safe(adev, save, &isp->devices, list) { ++ pm_runtime_disable(&adev->auxdev.dev); ++ list_del(&adev->list); ++ auxiliary_device_delete(&adev->auxdev); ++ auxiliary_device_uninit(&adev->auxdev); ++ } ++ ++ mutex_unlock(&ipu6_bus_mutex); ++} +diff --git a/drivers/media/pci/intel/ipu6/ipu6-bus.h b/drivers/media/pci/intel/ipu6/ipu6-bus.h +new file mode 100644 +index 000000000000..d46181354836 +--- /dev/null ++++ b/drivers/media/pci/intel/ipu6/ipu6-bus.h +@@ -0,0 +1,58 @@ ++/* SPDX-License-Identifier: GPL-2.0-only */ ++/* Copyright (C) 2013 - 2023 Intel Corporation */ ++ ++#ifndef IPU6_BUS_H ++#define IPU6_BUS_H ++ ++#include <linux/auxiliary_bus.h> ++#include <linux/container_of.h> ++#include <linux/device.h> ++#include <linux/irqreturn.h> ++#include <linux/list.h> ++#include <linux/scatterlist.h> ++#include <linux/types.h> ++ ++struct firmware; ++struct pci_dev; ++ ++#define IPU6_BUS_NAME IPU6_NAME "-bus" ++ ++struct ipu6_buttress_ctrl; ++ ++struct ipu6_bus_device { ++ struct auxiliary_device auxdev; ++ struct auxiliary_driver *auxdrv; ++ const struct ipu6_auxdrv_data *auxdrv_data; ++ struct list_head list; ++ void *pdata; ++ struct ipu6_mmu *mmu; ++ struct ipu6_device *isp; ++ struct ipu6_buttress_ctrl *ctrl; ++ u64 dma_mask; ++ const struct firmware *fw; ++ struct sg_table fw_sgt; ++ u64 *pkg_dir; ++ dma_addr_t pkg_dir_dma_addr; ++ unsigned int pkg_dir_size; ++}; ++ ++struct ipu6_auxdrv_data { ++ irqreturn_t (*isr)(struct ipu6_bus_device *adev); ++ irqreturn_t (*isr_threaded)(struct ipu6_bus_device *adev); ++ bool wake_isr_thread; ++}; ++ ++#define to_ipu6_bus_device(_dev) \ ++ container_of(to_auxiliary_dev(_dev), struct ipu6_bus_device, auxdev) ++#define auxdev_to_adev(_auxdev) \ ++ container_of(_auxdev, struct ipu6_bus_device, auxdev) ++#define ipu6_bus_get_drvdata(adev) dev_get_drvdata(&(adev)->auxdev.dev) ++ ++struct ipu6_bus_device * ++ipu6_bus_initialize_device(struct pci_dev *pdev, struct device *parent, ++ void *pdata, struct ipu6_buttress_ctrl *ctrl, ++ char *name); ++int ipu6_bus_add_device(struct ipu6_bus_device *adev); ++void ipu6_bus_del_devices(struct pci_dev *pdev); ++ ++#endif +-- +2.43.0 + +From 5836e6b1138ff9cba76ab74c1eab0ec57e50dfc1 Mon Sep 17 00:00:00 2001 +From: Bingbu Cao <bingbu.cao@intel.com> +Date: Thu, 11 Jan 2024 14:55:17 +0800 +Subject: [PATCH 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 <bingbu.cao@intel.com> +Link: https://lore.kernel.org/r/20240111065531.2418836-4-bingbu.cao@intel.com +Signed-off-by: Hans de Goede <hdegoede@redhat.com> +--- + drivers/media/pci/intel/ipu6/ipu6-buttress.c | 912 ++++++++++++++++++ + drivers/media/pci/intel/ipu6/ipu6-buttress.h | 102 ++ + .../intel/ipu6/ipu6-platform-buttress-regs.h | 232 +++++ + 3 files changed, 1246 insertions(+) + create mode 100644 drivers/media/pci/intel/ipu6/ipu6-buttress.c + create mode 100644 drivers/media/pci/intel/ipu6/ipu6-buttress.h + create mode 100644 drivers/media/pci/intel/ipu6/ipu6-platform-buttress-regs.h + +diff --git a/drivers/media/pci/intel/ipu6/ipu6-buttress.c b/drivers/media/pci/intel/ipu6/ipu6-buttress.c +new file mode 100644 +index 000000000000..2f73302812f3 +--- /dev/null ++++ b/drivers/media/pci/intel/ipu6/ipu6-buttress.c +@@ -0,0 +1,912 @@ ++// SPDX-License-Identifier: GPL-2.0-only ++/* ++ * Copyright (C) 2013 - 2023 Intel Corporation ++ */ ++ ++#include <linux/bitfield.h> ++#include <linux/bits.h> ++#include <linux/completion.h> ++#include <linux/delay.h> ++#include <linux/device.h> ++#include <linux/dma-mapping.h> ++#include <linux/firmware.h> ++#include <linux/interrupt.h> ++#include <linux/iopoll.h> ++#include <linux/math64.h> ++#include <linux/mm.h> ++#include <linux/mutex.h> ++#include <linux/pci.h> ++#include <linux/pfn.h> ++#include <linux/pm_runtime.h> ++#include <linux/scatterlist.h> ++#include <linux/slab.h> ++#include <linux/time64.h> ++ ++#include "ipu6.h" ++#include "ipu6-bus.h" ++#include "ipu6-buttress.h" ++#include "ipu6-platform-buttress-regs.h" ++ ++#define BOOTLOADER_STATUS_OFFSET 0x15c ++ ++#define BOOTLOADER_MAGIC_KEY 0xb00710ad ++ ++#define ENTRY BUTTRESS_IU2CSECSR_IPC_PEER_COMP_ACTIONS_RST_PHASE1 ++#define EXIT BUTTRESS_IU2CSECSR_IPC_PEER_COMP_ACTIONS_RST_PHASE2 ++#define QUERY BUTTRESS_IU2CSECSR_IPC_PEER_QUERIED_IP_COMP_ACTIONS_RST_PHASE ++ ++#define BUTTRESS_TSC_SYNC_RESET_TRIAL_MAX 10 ++ ++#define BUTTRESS_POWER_TIMEOUT_US (200 * USEC_PER_MSEC) ++ ++#define BUTTRESS_CSE_BOOTLOAD_TIMEOUT_US (5 * USEC_PER_SEC) ++#define BUTTRESS_CSE_AUTHENTICATE_TIMEOUT_US (10 * USEC_PER_SEC) ++#define BUTTRESS_CSE_FWRESET_TIMEOUT_US (100 * USEC_PER_MSEC) ++ ++#define BUTTRESS_IPC_TX_TIMEOUT_MS MSEC_PER_SEC ++#define BUTTRESS_IPC_RX_TIMEOUT_MS MSEC_PER_SEC ++#define BUTTRESS_IPC_VALIDITY_TIMEOUT_US (1 * USEC_PER_SEC) ++#define BUTTRESS_TSC_SYNC_TIMEOUT_US (5 * USEC_PER_MSEC) ++ ++#define BUTTRESS_IPC_RESET_RETRY 2000 ++#define BUTTRESS_CSE_IPC_RESET_RETRY 4 ++#define BUTTRESS_IPC_CMD_SEND_RETRY 1 ++ ++#define BUTTRESS_MAX_CONSECUTIVE_IRQS 100 ++ ++static const u32 ipu6_adev_irq_mask[2] = { ++ BUTTRESS_ISR_IS_IRQ, ++ BUTTRESS_ISR_PS_IRQ ++}; ++ ++int ipu6_buttress_ipc_reset(struct ipu6_device *isp, ++ struct ipu6_buttress_ipc *ipc) ++{ ++ unsigned int retries = BUTTRESS_IPC_RESET_RETRY; ++ struct ipu6_buttress *b = &isp->buttress; ++ u32 val = 0, csr_in_clr; ++ ++ if (!isp->secure_mode) { ++ dev_dbg(&isp->pdev->dev, "Skip IPC reset for non-secure mode"); ++ return 0; ++ } ++ ++ mutex_lock(&b->ipc_mutex); ++ ++ /* Clear-by-1 CSR (all bits), corresponding internal states. */ ++ val = readl(isp->base + ipc->csr_in); ++ writel(val, isp->base + ipc->csr_in); ++ ++ /* Set peer CSR bit IPC_PEER_COMP_ACTIONS_RST_PHASE1 */ ++ writel(ENTRY, isp->base + ipc->csr_out); ++ /* ++ * Clear-by-1 all CSR bits EXCEPT following ++ * bits: ++ * A. IPC_PEER_COMP_ACTIONS_RST_PHASE1. ++ * B. IPC_PEER_COMP_ACTIONS_RST_PHASE2. ++ * C. Possibly custom bits, depending on ++ * their role. ++ */ ++ csr_in_clr = BUTTRESS_IU2CSECSR_IPC_PEER_DEASSERTED_REG_VALID_REQ | ++ BUTTRESS_IU2CSECSR_IPC_PEER_ACKED_REG_VALID | ++ BUTTRESS_IU2CSECSR_IPC_PEER_ASSERTED_REG_VALID_REQ | QUERY; ++ ++ do { ++ usleep_range(400, 500); ++ val = readl(isp->base + ipc->csr_in); ++ switch (val) { ++ case ENTRY | EXIT: ++ case ENTRY | EXIT | QUERY: ++ /* ++ * 1) Clear-by-1 CSR bits ++ * (IPC_PEER_COMP_ACTIONS_RST_PHASE1, ++ * IPC_PEER_COMP_ACTIONS_RST_PHASE2). ++ * 2) Set peer CSR bit ++ * IPC_PEER_QUERIED_IP_COMP_ACTIONS_RST_PHASE. ++ */ ++ writel(ENTRY | EXIT, isp->base + ipc->csr_in); ++ writel(QUERY, isp->base + ipc->csr_out); ++ break; ++ case ENTRY: ++ case ENTRY | QUERY: ++ /* ++ * 1) Clear-by-1 CSR bits ++ * (IPC_PEER_COMP_ACTIONS_RST_PHASE1, ++ * IPC_PEER_QUERIED_IP_COMP_ACTIONS_RST_PHASE). ++ * 2) Set peer CSR bit ++ * IPC_PEER_COMP_ACTIONS_RST_PHASE1. ++ */ ++ writel(ENTRY | QUERY, isp->base + ipc->csr_in); ++ writel(ENTRY, isp->base + ipc->csr_out); ++ break; ++ case EXIT: ++ case EXIT | QUERY: ++ /* ++ * Clear-by-1 CSR bit ++ * IPC_PEER_COMP_ACTIONS_RST_PHASE2. ++ * 1) Clear incoming doorbell. ++ * 2) Clear-by-1 all CSR bits EXCEPT following ++ * bits: ++ * A. IPC_PEER_COMP_ACTIONS_RST_PHASE1. ++ * B. IPC_PEER_COMP_ACTIONS_RST_PHASE2. ++ * C. Possibly custom bits, depending on ++ * their role. ++ * 3) Set peer CSR bit ++ * IPC_PEER_COMP_ACTIONS_RST_PHASE2. ++ */ ++ writel(EXIT, isp->base + ipc->csr_in); ++ writel(0, isp->base + ipc->db0_in); ++ writel(csr_in_clr, isp->base + ipc->csr_in); ++ writel(EXIT, isp->base + ipc->csr_out); ++ ++ /* ++ * Read csr_in again to make sure if RST_PHASE2 is done. ++ * If csr_in is QUERY, it should be handled again. ++ */ ++ usleep_range(200, 300); ++ val = readl(isp->base + ipc->csr_in); ++ if (val & QUERY) { ++ dev_dbg(&isp->pdev->dev, ++ "RST_PHASE2 retry csr_in = %x\n", val); ++ break; ++ } ++ mutex_unlock(&b->ipc_mutex); ++ return 0; ++ case QUERY: ++ /* ++ * 1) Clear-by-1 CSR bit ++ * IPC_PEER_QUERIED_IP_COMP_ACTIONS_RST_PHASE. ++ * 2) Set peer CSR bit ++ * IPC_PEER_COMP_ACTIONS_RST_PHASE1 ++ */ ++ writel(QUERY, isp->base + ipc->csr_in); ++ writel(ENTRY, isp->base + ipc->csr_out); ++ break; ++ default: ++ dev_warn_ratelimited(&isp->pdev->dev, ++ "Unexpected CSR 0x%x\n", val); ++ break; ++ } ++ } while (retries--); ++ ++ mutex_unlock(&b->ipc_mutex); ++ dev_err(&isp->pdev->dev, "Timed out while waiting for CSE\n"); ++ ++ return -ETIMEDOUT; ++} ++ ++static void ipu6_buttress_ipc_validity_close(struct ipu6_device *isp, ++ struct ipu6_buttress_ipc *ipc) ++{ ++ writel(BUTTRESS_IU2CSECSR_IPC_PEER_DEASSERTED_REG_VALID_REQ, ++ isp->base + ipc->csr_out); ++} ++ ++static int ++ipu6_buttress_ipc_validity_open(struct ipu6_device *isp, ++ struct ipu6_buttress_ipc *ipc) ++{ ++ unsigned int mask = BUTTRESS_IU2CSECSR_IPC_PEER_ACKED_REG_VALID; ++ void __iomem *addr; ++ int ret; ++ u32 val; ++ ++ writel(BUTTRESS_IU2CSECSR_IPC_PEER_ASSERTED_REG_VALID_REQ, ++ isp->base + ipc->csr_out); ++ ++ addr = isp->base + ipc->csr_in; ++ ret = readl_poll_timeout(addr, val, val & mask, 200, ++ BUTTRESS_IPC_VALIDITY_TIMEOUT_US); ++ if (ret) { ++ dev_err(&isp->pdev->dev, "CSE validity timeout 0x%x\n", val); ++ ipu6_buttress_ipc_validity_close(isp, ipc); ++ } ++ ++ return ret; ++} ++ ++static void ipu6_buttress_ipc_recv(struct ipu6_device *isp, ++ struct ipu6_buttress_ipc *ipc, u32 *ipc_msg) ++{ ++ if (ipc_msg) ++ *ipc_msg = readl(isp->base + ipc->data0_in); ++ writel(0, isp->base + ipc->db0_in); ++} ++ ++static int ipu6_buttress_ipc_send_bulk(struct ipu6_device *isp, ++ enum ipu6_buttress_ipc_domain ipc_domain, ++ struct ipu6_ipc_buttress_bulk_msg *msgs, ++ u32 size) ++{ ++ unsigned long tx_timeout_jiffies, rx_timeout_jiffies; ++ unsigned int i, retry = BUTTRESS_IPC_CMD_SEND_RETRY; ++ struct ipu6_buttress *b = &isp->buttress; ++ struct ipu6_buttress_ipc *ipc; ++ u32 val; ++ int ret; ++ int tout; ++ ++ ipc = ipc_domain == IPU6_BUTTRESS_IPC_CSE ? &b->cse : &b->ish; ++ ++ mutex_lock(&b->ipc_mutex); ++ ++ ret = ipu6_buttress_ipc_validity_open(isp, ipc); ++ if (ret) { ++ dev_err(&isp->pdev->dev, "IPC validity open failed\n"); ++ goto out; ++ } ++ ++ tx_timeout_jiffies = msecs_to_jiffies(BUTTRESS_IPC_TX_TIMEOUT_MS); ++ rx_timeout_jiffies = msecs_to_jiffies(BUTTRESS_IPC_RX_TIMEOUT_MS); ++ ++ for (i = 0; i < size; i++) { ++ reinit_completion(&ipc->send_complete); ++ if (msgs[i].require_resp) ++ reinit_completion(&ipc->recv_complete); ++ ++ dev_dbg(&isp->pdev->dev, "bulk IPC command: 0x%x\n", ++ msgs[i].cmd); ++ writel(msgs[i].cmd, isp->base + ipc->data0_out); ++ val = BUTTRESS_IU2CSEDB0_BUSY | msgs[i].cmd_size; ++ writel(val, isp->base + ipc->db0_out); ++ ++ tout = wait_for_completion_timeout(&ipc->send_complete, ++ tx_timeout_jiffies); ++ if (!tout) { ++ dev_err(&isp->pdev->dev, "send IPC response timeout\n"); ++ if (!retry--) { ++ ret = -ETIMEDOUT; ++ goto out; ++ } ++ ++ /* Try again if CSE is not responding on first try */ ++ writel(0, isp->base + ipc->db0_out); ++ i--; ++ continue; ++ } ++ ++ retry = BUTTRESS_IPC_CMD_SEND_RETRY; ++ ++ if (!msgs[i].require_resp) ++ continue; ++ ++ tout = wait_for_completion_timeout(&ipc->recv_complete, ++ rx_timeout_jiffies); ++ if (!tout) { ++ dev_err(&isp->pdev->dev, "recv IPC response timeout\n"); ++ ret = -ETIMEDOUT; ++ goto out; ++ } ++ ++ if (ipc->nack_mask && ++ (ipc->recv_data & ipc->nack_mask) == ipc->nack) { ++ dev_err(&isp->pdev->dev, ++ "IPC NACK for cmd 0x%x\n", msgs[i].cmd); ++ ret = -EIO; ++ goto out; ++ } ++ ++ if (ipc->recv_data != msgs[i].expected_resp) { ++ dev_err(&isp->pdev->dev, ++ "expected resp: 0x%x, IPC response: 0x%x ", ++ msgs[i].expected_resp, ipc->recv_data); ++ ret = -EIO; ++ goto out; ++ } ++ } ++ ++ dev_dbg(&isp->pdev->dev, "bulk IPC commands done\n"); ++ ++out: ++ ipu6_buttress_ipc_validity_close(isp, ipc); ++ mutex_unlock(&b->ipc_mutex); ++ return ret; ++} ++ ++static int ++ipu6_buttress_ipc_send(struct ipu6_device *isp, ++ enum ipu6_buttress_ipc_domain ipc_domain, ++ u32 ipc_msg, u32 size, bool require_resp, ++ u32 expected_resp) ++{ ++ struct ipu6_ipc_buttress_bulk_msg msg = { ++ .cmd = ipc_msg, ++ .cmd_size = size, ++ .require_resp = require_resp, ++ .expected_resp = expected_resp, ++ }; ++ ++ return ipu6_buttress_ipc_send_bulk(isp, ipc_domain, &msg, 1); ++} ++ ++static irqreturn_t ipu6_buttress_call_isr(struct ipu6_bus_device *adev) ++{ ++ irqreturn_t ret = IRQ_WAKE_THREAD; ++ ++ if (!adev || !adev->auxdrv || !adev->auxdrv_data) ++ return IRQ_NONE; ++ ++ if (adev->auxdrv_data->isr) ++ ret = adev->auxdrv_data->isr(adev); ++ ++ if (ret == IRQ_WAKE_THREAD && !adev->auxdrv_data->isr_threaded) ++ ret = IRQ_NONE; ++ ++ return ret; ++} ++ ++irqreturn_t ipu6_buttress_isr(int irq, void *isp_ptr) ++{ ++ struct ipu6_device *isp = isp_ptr; ++ struct ipu6_bus_device *adev[] = { isp->isys, isp->psys }; ++ struct ipu6_buttress *b = &isp->buttress; ++ u32 reg_irq_sts = BUTTRESS_REG_ISR_STATUS; ++ irqreturn_t ret = IRQ_NONE; ++ u32 disable_irqs = 0; ++ u32 irq_status; ++ u32 i, count = 0; ++ ++ pm_runtime_get_noresume(&isp->pdev->dev); ++ ++ irq_status = readl(isp->base + reg_irq_sts); ++ if (!irq_status) { ++ pm_runtime_put_noidle(&isp->pdev->dev); ++ return IRQ_NONE; ++ } ++ ++ do { ++ writel(irq_status, isp->base + BUTTRESS_REG_ISR_CLEAR); ++ ++ for (i = 0; i < ARRAY_SIZE(ipu6_adev_irq_mask); i++) { ++ irqreturn_t r = ipu6_buttress_call_isr(adev[i]); ++ ++ if (!(irq_status & ipu6_adev_irq_mask[i])) ++ continue; ++ ++ if (r == IRQ_WAKE_THREAD) { ++ ret = IRQ_WAKE_THREAD; ++ disable_irqs |= ipu6_adev_irq_mask[i]; ++ } else if (ret == IRQ_NONE && r == IRQ_HANDLED) { ++ ret = IRQ_HANDLED; ++ } ++ } ++ ++ if ((irq_status & BUTTRESS_EVENT) && ret == IRQ_NONE) ++ ret = IRQ_HANDLED; ++ ++ if (irq_status & BUTTRESS_ISR_IPC_FROM_CSE_IS_WAITING) { ++ dev_dbg(&isp->pdev->dev, ++ "BUTTRESS_ISR_IPC_FROM_CSE_IS_WAITING\n"); ++ ipu6_buttress_ipc_recv(isp, &b->cse, &b->cse.recv_data); ++ complete(&b->cse.recv_complete); ++ } ++ ++ if (irq_status & BUTTRESS_ISR_IPC_FROM_ISH_IS_WAITING) { ++ dev_dbg(&isp->pdev->dev, ++ "BUTTRESS_ISR_IPC_FROM_ISH_IS_WAITING\n"); ++ ipu6_buttress_ipc_recv(isp, &b->ish, &b->ish.recv_data); ++ complete(&b->ish.recv_complete); ++ } ++ ++ if (irq_status & BUTTRESS_ISR_IPC_EXEC_DONE_BY_CSE) { ++ dev_dbg(&isp->pdev->dev, ++ "BUTTRESS_ISR_IPC_EXEC_DONE_BY_CSE\n"); ++ complete(&b->cse.send_complete); ++ } ++ ++ if (irq_status & BUTTRESS_ISR_IPC_EXEC_DONE_BY_ISH) { ++ dev_dbg(&isp->pdev->dev, ++ "BUTTRESS_ISR_IPC_EXEC_DONE_BY_CSE\n"); ++ complete(&b->ish.send_complete); ++ } ++ ++ if (irq_status & BUTTRESS_ISR_SAI_VIOLATION && ++ ipu6_buttress_get_secure_mode(isp)) ++ dev_err(&isp->pdev->dev, ++ "BUTTRESS_ISR_SAI_VIOLATION\n"); ++ ++ if (irq_status & (BUTTRESS_ISR_IS_FATAL_MEM_ERR | ++ BUTTRESS_ISR_PS_FATAL_MEM_ERR)) ++ dev_err(&isp->pdev->dev, ++ "BUTTRESS_ISR_FATAL_MEM_ERR\n"); ++ ++ if (irq_status & BUTTRESS_ISR_UFI_ERROR) ++ dev_err(&isp->pdev->dev, "BUTTRESS_ISR_UFI_ERROR\n"); ++ ++ if (++count == BUTTRESS_MAX_CONSECUTIVE_IRQS) { ++ dev_err(&isp->pdev->dev, "too many consecutive IRQs\n"); ++ ret = IRQ_NONE; ++ break; ++ } ++ ++ irq_status = readl(isp->base + reg_irq_sts); ++ } while (irq_status); ++ ++ if (disable_irqs) ++ writel(BUTTRESS_IRQS & ~disable_irqs, ++ isp->base + BUTTRESS_REG_ISR_ENABLE); ++ ++ pm_runtime_put(&isp->pdev->dev); ++ ++ return ret; ++} ++ ++irqreturn_t ipu6_buttress_isr_threaded(int irq, void *isp_ptr) ++{ ++ struct ipu6_device *isp = isp_ptr; ++ struct ipu6_bus_device *adev[] = { isp->isys, isp->psys }; ++ const struct ipu6_auxdrv_data *drv_data = NULL; ++ irqreturn_t ret = IRQ_NONE; ++ unsigned int i; ++ ++ for (i = 0; i < ARRAY_SIZE(ipu6_adev_irq_mask) && adev[i]; i++) { ++ drv_data = adev[i]->auxdrv_data; ++ if (!drv_data) ++ continue; ++ ++ if (drv_data->wake_isr_thread && ++ drv_data->isr_threaded(adev[i]) == IRQ_HANDLED) ++ ret = IRQ_HANDLED; ++ } ++ ++ writel(BUTTRESS_IRQS, isp->base + BUTTRESS_REG_ISR_ENABLE); ++ ++ return ret; ++} ++ ++int ipu6_buttress_power(struct device *dev, struct ipu6_buttress_ctrl *ctrl, ++ bool on) ++{ ++ struct ipu6_device *isp = to_ipu6_bus_device(dev)->isp; ++ u32 pwr_sts, val; ++ int ret; ++ ++ if (!ctrl) ++ return 0; ++ ++ mutex_lock(&isp->buttress.power_mutex); ++ ++ if (!on) { ++ val = 0; ++ pwr_sts = ctrl->pwr_sts_off << ctrl->pwr_sts_shift; ++ } else { ++ val = BUTTRESS_FREQ_CTL_START | ++ FIELD_PREP(BUTTRESS_FREQ_CTL_RATIO_MASK, ++ ctrl->ratio) | ++ FIELD_PREP(BUTTRESS_FREQ_CTL_QOS_FLOOR_MASK, ++ ctrl->qos_floor) | ++ BUTTRESS_FREQ_CTL_ICCMAX_LEVEL; ++ ++ pwr_sts = ctrl->pwr_sts_on << ctrl->pwr_sts_shift; ++ } ++ ++ writel(val, isp->base + ctrl->freq_ctl); ++ ++ ret = readl_poll_timeout(isp->base + BUTTRESS_REG_PWR_STATE, ++ val, (val & ctrl->pwr_sts_mask) == pwr_sts, ++ 100, BUTTRESS_POWER_TIMEOUT_US); ++ if (ret) ++ dev_err(&isp->pdev->dev, ++ "Change power status timeout with 0x%x\n", val); ++ ++ ctrl->started = !ret && on; ++ ++ mutex_unlock(&isp->buttress.power_mutex); ++ ++ return ret; ++} ++ ++bool ipu6_buttress_get_secure_mode(struct ipu6_device *isp) ++{ ++ u32 val; ++ ++ val = readl(isp->base + BUTTRESS_REG_SECURITY_CTL); ++ ++ return val & BUTTRESS_SECURITY_CTL_FW_SECURE_MODE; ++} ++ ++bool ipu6_buttress_auth_done(struct ipu6_device *isp) ++{ ++ u32 val; ++ ++ if (!isp->secure_mode) ++ return true; ++ ++ val = readl(isp->base + BUTTRESS_REG_SECURITY_CTL); ++ val = FIELD_GET(BUTTRESS_SECURITY_CTL_FW_SETUP_MASK, val); ++ ++ return val == BUTTRESS_SECURITY_CTL_AUTH_DONE; ++} ++EXPORT_SYMBOL_NS_GPL(ipu6_buttress_auth_done, INTEL_IPU6); ++ ++int ipu6_buttress_reset_authentication(struct ipu6_device *isp) ++{ ++ int ret; ++ u32 val; ++ ++ if (!isp->secure_mode) { ++ dev_dbg(&isp->pdev->dev, "Skip auth for non-secure mode\n"); ++ return 0; ++ } ++ ++ writel(BUTTRESS_FW_RESET_CTL_START, isp->base + ++ BUTTRESS_REG_FW_RESET_CTL); ++ ++ ret = readl_poll_timeout(isp->base + BUTTRESS_REG_FW_RESET_CTL, val, ++ val & BUTTRESS_FW_RESET_CTL_DONE, 500, ++ BUTTRESS_CSE_FWRESET_TIMEOUT_US); ++ if (ret) { ++ dev_err(&isp->pdev->dev, ++ "Time out while resetting authentication state\n"); ++ return ret; ++ } ++ ++ dev_dbg(&isp->pdev->dev, "FW reset for authentication done\n"); ++ writel(0, isp->base + BUTTRESS_REG_FW_RESET_CTL); ++ /* leave some time for HW restore */ ++ usleep_range(800, 1000); ++ ++ return 0; ++} ++ ++int ipu6_buttress_map_fw_image(struct ipu6_bus_device *sys, ++ const struct firmware *fw, struct sg_table *sgt) ++{ ++ struct page **pages; ++ const void *addr; ++ unsigned long n_pages; ++ unsigned int i; ++ int ret; ++ ++ n_pages = PHYS_PFN(PAGE_ALIGN(fw->size)); ++ ++ pages = kmalloc_array(n_pages, sizeof(*pages), GFP_KERNEL); ++ if (!pages) ++ return -ENOMEM; ++ ++ addr = fw->data; ++ for (i = 0; i < n_pages; i++) { ++ struct page *p = vmalloc_to_page(addr); ++ ++ if (!p) { ++ ret = -ENOMEM; ++ goto out; ++ } ++ pages[i] = p; ++ addr += PAGE_SIZE; ++ } ++ ++ ret = sg_alloc_table_from_pages(sgt, pages, n_pages, 0, fw->size, ++ GFP_KERNEL); ++ if (ret) { ++ ret = -ENOMEM; ++ goto out; ++ } ++ ++ ret = dma_map_sgtable(&sys->auxdev.dev, sgt, DMA_TO_DEVICE, 0); ++ if (ret < 0) { ++ ret = -ENOMEM; ++ sg_free_table(sgt); ++ goto out; ++ } ++ ++ dma_sync_sgtable_for_device(&sys->auxdev.dev, sgt, DMA_TO_DEVICE); ++ ++out: ++ kfree(pages); ++ ++ return ret; ++} ++EXPORT_SYMBOL_NS_GPL(ipu6_buttress_map_fw_image, INTEL_IPU6); ++ ++void ipu6_buttress_unmap_fw_image(struct ipu6_bus_device *sys, ++ struct sg_table *sgt) ++{ ++ dma_unmap_sgtable(&sys->auxdev.dev, sgt, DMA_TO_DEVICE, 0); ++ sg_free_table(sgt); ++} ++EXPORT_SYMBOL_NS_GPL(ipu6_buttress_unmap_fw_image, INTEL_IPU6); ++ ++int ipu6_buttress_authenticate(struct ipu6_device *isp) ++{ ++ struct ipu6_buttress *b = &isp->buttress; ++ struct ipu6_psys_pdata *psys_pdata; ++ u32 data, mask, done, fail; ++ int ret; ++ ++ if (!isp->secure_mode) { ++ dev_dbg(&isp->pdev->dev, "Skip auth for non-secure mode\n"); ++ return 0; ++ } ++ ++ psys_pdata = isp->psys->pdata; ++ ++ mutex_lock(&b->auth_mutex); ++ ++ if (ipu6_buttress_auth_done(isp)) { ++ ret = 0; ++ goto out_unlock; ++ } ++ ++ /* ++ * Write address of FIT table to FW_SOURCE register ++ * Let's use fw address. I.e. not using FIT table yet ++ */ ++ data = lower_32_bits(isp->psys->pkg_dir_dma_addr); ++ writel(data, isp->base + BUTTRESS_REG_FW_SOURCE_BASE_LO); ++ ++ data = upper_32_bits(isp->psys->pkg_dir_dma_addr); ++ writel(data, isp->base + BUTTRESS_REG_FW_SOURCE_BASE_HI); ++ ++ /* ++ * Write boot_load into IU2CSEDATA0 ++ * Write sizeof(boot_load) | 0x2 << CLIENT_ID to ++ * IU2CSEDB.IU2CSECMD and set IU2CSEDB.IU2CSEBUSY as ++ */ ++ dev_info(&isp->pdev->dev, "Sending BOOT_LOAD to CSE\n"); ++ ++ ret = ipu6_buttress_ipc_send(isp, IPU6_BUTTRESS_IPC_CSE, ++ BUTTRESS_IU2CSEDATA0_IPC_BOOT_LOAD, ++ 1, true, ++ BUTTRESS_CSE2IUDATA0_IPC_BOOT_LOAD_DONE); ++ if (ret) { ++ dev_err(&isp->pdev->dev, "CSE boot_load failed\n"); ++ goto out_unlock; ++ } ++ ++ mask = BUTTRESS_SECURITY_CTL_FW_SETUP_MASK; ++ done = BUTTRESS_SECURITY_CTL_FW_SETUP_DONE; ++ fail = BUTTRESS_SECURITY_CTL_AUTH_FAILED; ++ ret = readl_poll_timeout(isp->base + BUTTRESS_REG_SECURITY_CTL, data, ++ ((data & mask) == done || ++ (data & mask) == fail), 500, ++ BUTTRESS_CSE_BOOTLOAD_TIMEOUT_US); ++ if (ret) { ++ dev_err(&isp->pdev->dev, "CSE boot_load timeout\n"); ++ goto out_unlock; ++ } ++ ++ if ((data & mask) == fail) { ++ dev_err(&isp->pdev->dev, "CSE auth failed\n"); ++ ret = -EINVAL; ++ goto out_unlock; ++ } ++ ++ ret = readl_poll_timeout(psys_pdata->base + BOOTLOADER_STATUS_OFFSET, ++ data, data == BOOTLOADER_MAGIC_KEY, 500, ++ BUTTRESS_CSE_BOOTLOAD_TIMEOUT_US); ++ if (ret) { ++ dev_err(&isp->pdev->dev, "Unexpected magic number 0x%x\n", ++ data); ++ goto out_unlock; ++ } ++ ++ /* ++ * Write authenticate_run into IU2CSEDATA0 ++ * Write sizeof(boot_load) | 0x2 << CLIENT_ID to ++ * IU2CSEDB.IU2CSECMD and set IU2CSEDB.IU2CSEBUSY as ++ */ ++ dev_info(&isp->pdev->dev, "Sending AUTHENTICATE_RUN to CSE\n"); ++ ret = ipu6_buttress_ipc_send(isp, IPU6_BUTTRESS_IPC_CSE, ++ BUTTRESS_IU2CSEDATA0_IPC_AUTH_RUN, ++ 1, true, ++ BUTTRESS_CSE2IUDATA0_IPC_AUTH_RUN_DONE); ++ if (ret) { ++ dev_err(&isp->pdev->dev, "CSE authenticate_run failed\n"); ++ goto out_unlock; ++ } ++ ++ done = BUTTRESS_SECURITY_CTL_AUTH_DONE; ++ ret = readl_poll_timeout(isp->base + BUTTRESS_REG_SECURITY_CTL, data, ++ ((data & mask) == done || ++ (data & mask) == fail), 500, ++ BUTTRESS_CSE_AUTHENTICATE_TIMEOUT_US); ++ if (ret) { ++ dev_err(&isp->pdev->dev, "CSE authenticate timeout\n"); ++ goto out_unlock; ++ } ++ ++ if ((data & mask) == fail) { ++ dev_err(&isp->pdev->dev, "CSE boot_load failed\n"); ++ ret = -EINVAL; ++ goto out_unlock; ++ } ++ ++ dev_info(&isp->pdev->dev, "CSE authenticate_run done\n"); ++ ++out_unlock: ++ mutex_unlock(&b->auth_mutex); ++ ++ return ret; ++} ++ ++static int ipu6_buttress_send_tsc_request(struct ipu6_device *isp) ++{ ++ u32 val, mask, done; ++ int ret; ++ ++ mask = BUTTRESS_PWR_STATE_HH_STATUS_MASK; ++ ++ writel(BUTTRESS_FABRIC_CMD_START_TSC_SYNC, ++ isp->base + BUTTRESS_REG_FABRIC_CMD); ++ ++ val = readl(isp->base + BUTTRESS_REG_PWR_STATE); ++ val = FIELD_GET(mask, val); ++ if (val == BUTTRESS_PWR_STATE_HH_STATE_ERR) { ++ dev_err(&isp->pdev->dev, "Start tsc sync failed\n"); ++ return -EINVAL; ++ } ++ ++ done = BUTTRESS_PWR_STATE_HH_STATE_DONE; ++ ret = readl_poll_timeout(isp->base + BUTTRESS_REG_PWR_STATE, val, ++ FIELD_GET(mask, val) == done, 500, ++ BUTTRESS_TSC_SYNC_TIMEOUT_US); ++ if (ret) ++ dev_err(&isp->pdev->dev, "Start tsc sync timeout\n"); ++ ++ return ret; ++} ++ ++int ipu6_buttress_start_tsc_sync(struct ipu6_device *isp) ++{ ++ unsigned int i; ++ ++ for (i = 0; i < BUTTRESS_TSC_SYNC_RESET_TRIAL_MAX; i++) { ++ u32 val; ++ int ret; ++ ++ ret = ipu6_buttress_send_tsc_request(isp); ++ if (ret != -ETIMEDOUT) ++ return ret; ++ ++ val = readl(isp->base + BUTTRESS_REG_TSW_CTL); ++ val = val | BUTTRESS_TSW_CTL_SOFT_RESET; ++ writel(val, isp->base + BUTTRESS_REG_TSW_CTL); ++ val = val & ~BUTTRESS_TSW_CTL_SOFT_RESET; ++ writel(val, isp->base + BUTTRESS_REG_TSW_CTL); ++ } ++ ++ dev_err(&isp->pdev->dev, "TSC sync failed (timeout)\n"); ++ ++ return -ETIMEDOUT; ++} ++EXPORT_SYMBOL_NS_GPL(ipu6_buttress_start_tsc_sync, INTEL_IPU6); ++ ++void ipu6_buttress_tsc_read(struct ipu6_device *isp, u64 *val) ++{ ++ u32 tsc_hi_1, tsc_hi_2, tsc_lo; ++ unsigned long flags; ++ ++ local_irq_save(flags); ++ tsc_hi_1 = readl(isp->base + BUTTRESS_REG_TSC_HI); ++ tsc_lo = readl(isp->base + BUTTRESS_REG_TSC_LO); ++ tsc_hi_2 = readl(isp->base + BUTTRESS_REG_TSC_HI); ++ if (tsc_hi_1 == tsc_hi_2) { ++ *val = (u64)tsc_hi_1 << 32 | tsc_lo; ++ } else { ++ /* Check if TSC has rolled over */ ++ if (tsc_lo & BIT(31)) ++ *val = (u64)tsc_hi_1 << 32 | tsc_lo; ++ else ++ *val = (u64)tsc_hi_2 << 32 | tsc_lo; ++ } ++ local_irq_restore(flags); ++} ++EXPORT_SYMBOL_NS_GPL(ipu6_buttress_tsc_read, INTEL_IPU6); ++ ++u64 ipu6_buttress_tsc_ticks_to_ns(u64 ticks, const struct ipu6_device *isp) ++{ ++ u64 ns = ticks * 10000; ++ ++ /* ++ * converting TSC tick count to ns is calculated by: ++ * Example (TSC clock frequency is 19.2MHz): ++ * ns = ticks * 1000 000 000 / 19.2Mhz ++ * = ticks * 1000 000 000 / 19200000Hz ++ * = ticks * 10000 / 192 ns ++ */ ++ return div_u64(ns, isp->buttress.ref_clk); ++} ++EXPORT_SYMBOL_NS_GPL(ipu6_buttress_tsc_ticks_to_ns, INTEL_IPU6); ++ ++void ipu6_buttress_restore(struct ipu6_device *isp) ++{ ++ struct ipu6_buttress *b = &isp->buttress; ++ ++ writel(BUTTRESS_IRQS, isp->base + BUTTRESS_REG_ISR_CLEAR); ++ writel(BUTTRESS_IRQS, isp->base + BUTTRESS_REG_ISR_ENABLE); ++ writel(b->wdt_cached_value, isp->base + BUTTRESS_REG_WDT); ++} ++ ++int ipu6_buttress_init(struct ipu6_device *isp) ++{ ++ int ret, ipc_reset_retry = BUTTRESS_CSE_IPC_RESET_RETRY; ++ struct ipu6_buttress *b = &isp->buttress; ++ u32 val; ++ ++ mutex_init(&b->power_mutex); ++ mutex_init(&b->auth_mutex); ++ mutex_init(&b->cons_mutex); ++ mutex_init(&b->ipc_mutex); ++ init_completion(&b->ish.send_complete); ++ init_completion(&b->cse.send_complete); ++ init_completion(&b->ish.recv_complete); ++ init_completion(&b->cse.recv_complete); ++ ++ b->cse.nack = BUTTRESS_CSE2IUDATA0_IPC_NACK; ++ b->cse.nack_mask = BUTTRESS_CSE2IUDATA0_IPC_NACK_MASK; ++ b->cse.csr_in = BUTTRESS_REG_CSE2IUCSR; ++ b->cse.csr_out = BUTTRESS_REG_IU2CSECSR; ++ b->cse.db0_in = BUTTRESS_REG_CSE2IUDB0; ++ b->cse.db0_out = BUTTRESS_REG_IU2CSEDB0; ++ b->cse.data0_in = BUTTRESS_REG_CSE2IUDATA0; ++ b->cse.data0_out = BUTTRESS_REG_IU2CSEDATA0; ++ ++ /* no ISH on IPU6 */ ++ memset(&b->ish, 0, sizeof(b->ish)); ++ INIT_LIST_HEAD(&b->constraints); ++ ++ isp->secure_mode = ipu6_buttress_get_secure_mode(isp); ++ dev_info(&isp->pdev->dev, "IPU6 in %s mode touch 0x%x mask 0x%x\n", ++ isp->secure_mode ? "secure" : "non-secure", ++ readl(isp->base + BUTTRESS_REG_SECURITY_TOUCH), ++ readl(isp->base + BUTTRESS_REG_CAMERA_MASK)); ++ ++ b->wdt_cached_value = readl(isp->base + BUTTRESS_REG_WDT); ++ writel(BUTTRESS_IRQS, isp->base + BUTTRESS_REG_ISR_CLEAR); ++ writel(BUTTRESS_IRQS, isp->base + BUTTRESS_REG_ISR_ENABLE); ++ ++ /* get ref_clk frequency by reading the indication in btrs control */ ++ val = readl(isp->base + BUTTRESS_REG_BTRS_CTRL); ++ val = FIELD_GET(BUTTRESS_REG_BTRS_CTRL_REF_CLK_IND, val); ++ ++ switch (val) { ++ case 0x0: ++ b->ref_clk = 240; ++ break; ++ case 0x1: ++ b->ref_clk = 192; ++ break; ++ case 0x2: ++ b->ref_clk = 384; ++ break; ++ default: ++ dev_warn(&isp->pdev->dev, ++ "Unsupported ref clock, use 19.2Mhz by default.\n"); ++ b->ref_clk = 192; ++ break; ++ } ++ ++ /* Retry couple of times in case of CSE initialization is delayed */ ++ do { ++ ret = ipu6_buttress_ipc_reset(isp, &b->cse); ++ if (ret) { ++ dev_warn(&isp->pdev->dev, ++ "IPC reset protocol failed, retrying\n"); ++ } else { ++ dev_dbg(&isp->pdev->dev, "IPC reset done\n"); ++ return 0; ++ } ++ } while (ipc_reset_retry--); ++ ++ dev_err(&isp->pdev->dev, "IPC reset protocol failed\n"); ++ ++ mutex_destroy(&b->power_mutex); ++ mutex_destroy(&b->auth_mutex); ++ mutex_destroy(&b->cons_mutex); ++ mutex_destroy(&b->ipc_mutex); ++ ++ return ret; ++} ++ ++void ipu6_buttress_exit(struct ipu6_device *isp) ++{ ++ struct ipu6_buttress *b = &isp->buttress; ++ ++ writel(0, isp->base + BUTTRESS_REG_ISR_ENABLE); ++ ++ mutex_destroy(&b->power_mutex); ++ mutex_destroy(&b->auth_mutex); ++ mutex_destroy(&b->cons_mutex); ++ mutex_destroy(&b->ipc_mutex); ++} +diff --git a/drivers/media/pci/intel/ipu6/ipu6-buttress.h b/drivers/media/pci/intel/ipu6/ipu6-buttress.h +new file mode 100644 +index 000000000000..558e1d70f4af +--- /dev/null ++++ b/drivers/media/pci/intel/ipu6/ipu6-buttress.h +@@ -0,0 +1,102 @@ ++/* SPDX-License-Identifier: GPL-2.0-only */ ++/* Copyright (C) 2013 - 2023 Intel Corporation */ ++ ++#ifndef IPU6_BUTTRESS_H ++#define IPU6_BUTTRESS_H ++ ++#include <linux/completion.h> ++#include <linux/irqreturn.h> ++#include <linux/list.h> ++#include <linux/mutex.h> ++ ++struct device; ++struct firmware; ++struct ipu6_device; ++struct ipu6_bus_device; ++ ++#define BUTTRESS_PS_FREQ_STEP 25U ++#define BUTTRESS_MIN_FORCE_PS_FREQ (BUTTRESS_PS_FREQ_STEP * 8) ++#define BUTTRESS_MAX_FORCE_PS_FREQ (BUTTRESS_PS_FREQ_STEP * 32) ++ ++#define BUTTRESS_IS_FREQ_STEP 25U ++#define BUTTRESS_MIN_FORCE_IS_FREQ (BUTTRESS_IS_FREQ_STEP * 8) ++#define BUTTRESS_MAX_FORCE_IS_FREQ (BUTTRESS_IS_FREQ_STEP * 22) ++ ++struct ipu6_buttress_ctrl { ++ u32 freq_ctl, pwr_sts_shift, pwr_sts_mask, pwr_sts_on, pwr_sts_off; ++ unsigned int ratio; ++ unsigned int qos_floor; ++ bool started; ++}; ++ ++struct ipu6_buttress_ipc { ++ struct completion send_complete; ++ struct completion recv_complete; ++ u32 nack; ++ u32 nack_mask; ++ u32 recv_data; ++ u32 csr_out; ++ u32 csr_in; ++ u32 db0_in; ++ u32 db0_out; ++ u32 data0_out; ++ u32 data0_in; ++}; ++ ++struct ipu6_buttress { ++ struct mutex power_mutex, auth_mutex, cons_mutex, ipc_mutex; ++ struct ipu6_buttress_ipc cse; ++ struct ipu6_buttress_ipc ish; ++ struct list_head constraints; ++ u32 wdt_cached_value; ++ bool force_suspend; ++ u32 ref_clk; ++}; ++ ++struct ipu6_buttress_sensor_clk_freq { ++ unsigned int rate; ++ unsigned int val; ++}; ++ ++enum ipu6_buttress_ipc_domain { ++ IPU6_BUTTRESS_IPC_CSE, ++ IPU6_BUTTRESS_IPC_ISH, ++}; ++ ++struct ipu6_buttress_constraint { ++ struct list_head list; ++ unsigned int min_freq; ++}; ++ ++struct ipu6_ipc_buttress_bulk_msg { ++ u32 cmd; ++ u32 expected_resp; ++ bool require_resp; ++ u8 cmd_size; ++}; ++ ++int ipu6_buttress_ipc_reset(struct ipu6_device *isp, ++ struct ipu6_buttress_ipc *ipc); ++int ipu6_buttress_map_fw_image(struct ipu6_bus_device *sys, ++ const struct firmware *fw, ++ struct sg_table *sgt); ++void ipu6_buttress_unmap_fw_image(struct ipu6_bus_device *sys, ++ struct sg_table *sgt); ++int ipu6_buttress_power(struct device *dev, struct ipu6_buttress_ctrl *ctrl, ++ bool on); ++bool ipu6_buttress_get_secure_mode(struct ipu6_device *isp); ++int ipu6_buttress_authenticate(struct ipu6_device *isp); ++int ipu6_buttress_reset_authentication(struct ipu6_device *isp); ++bool ipu6_buttress_auth_done(struct ipu6_device *isp); ++int ipu6_buttress_start_tsc_sync(struct ipu6_device *isp); ++void ipu6_buttress_tsc_read(struct ipu6_device *isp, u64 *val); ++u64 ipu6_buttress_tsc_ticks_to_ns(u64 ticks, const struct ipu6_device *isp); ++ ++irqreturn_t ipu6_buttress_isr(int irq, void *isp_ptr); ++irqreturn_t ipu6_buttress_isr_threaded(int irq, void *isp_ptr); ++int ipu6_buttress_init(struct ipu6_device *isp); ++void ipu6_buttress_exit(struct ipu6_device *isp); ++void ipu6_buttress_csi_port_config(struct ipu6_device *isp, ++ u32 legacy, u32 combo); ++void ipu6_buttress_restore(struct ipu6_device *isp); ++#endif /* IPU6_BUTTRESS_H */ +diff --git a/drivers/media/pci/intel/ipu6/ipu6-platform-buttress-regs.h b/drivers/media/pci/intel/ipu6/ipu6-platform-buttress-regs.h +new file mode 100644 +index 000000000000..87239af96502 +--- /dev/null ++++ b/drivers/media/pci/intel/ipu6/ipu6-platform-buttress-regs.h +@@ -0,0 +1,232 @@ ++/* SPDX-License-Identifier: GPL-2.0-only */ ++/* Copyright (C) 2023 Intel Corporation */ ++ ++#ifndef IPU6_PLATFORM_BUTTRESS_REGS_H ++#define IPU6_PLATFORM_BUTTRESS_REGS_H ++ ++#include <linux/bits.h> ++ ++/* IS_WORKPOINT_REQ */ ++#define IPU6_BUTTRESS_REG_IS_FREQ_CTL 0x34 ++/* PS_WORKPOINT_REQ */ ++#define IPU6_BUTTRESS_REG_PS_FREQ_CTL 0x38 ++ ++#define IPU6_IS_FREQ_MAX 533 ++#define IPU6_IS_FREQ_MIN 200 ++#define IPU6_PS_FREQ_MAX 450 ++#define IPU6_IS_FREQ_RATIO_BASE 25 ++#define IPU6_PS_FREQ_RATIO_BASE 25 ++ ++/* should be tuned for real silicon */ ++#define IPU6_IS_FREQ_CTL_DEFAULT_RATIO 0x08 ++#define IPU6SE_IS_FREQ_CTL_DEFAULT_RATIO 0x0a ++#define IPU6_PS_FREQ_CTL_DEFAULT_RATIO 0x0d ++ ++#define IPU6_IS_FREQ_CTL_DEFAULT_QOS_FLOOR_RATIO 0x10 ++#define IPU6_PS_FREQ_CTL_DEFAULT_QOS_FLOOR_RATIO 0x0708 ++ ++#define IPU6_BUTTRESS_PWR_STATE_IS_PWR_SHIFT 3 ++#define IPU6_BUTTRESS_PWR_STATE_IS_PWR_MASK GENMASK(4, 3) ++ ++#define IPU6_BUTTRESS_PWR_STATE_PS_PWR_SHIFT 6 ++#define IPU6_BUTTRESS_PWR_STATE_PS_PWR_MASK GENMASK(7, 6) ++ ++#define IPU6_BUTTRESS_PWR_STATE_DN_DONE 0x0 ++#define IPU6_BUTTRESS_PWR_STATE_UP_PROCESS 0x1 ++#define IPU6_BUTTRESS_PWR_STATE_DN_PROCESS 0x2 ++#define IPU6_BUTTRESS_PWR_STATE_UP_DONE 0x3 ++ ++#define IPU6_BUTTRESS_REG_FPGA_SUPPORT_0 0x270 ++#define IPU6_BUTTRESS_REG_FPGA_SUPPORT_1 0x274 ++#define IPU6_BUTTRESS_REG_FPGA_SUPPORT_2 0x278 ++#define IPU6_BUTTRESS_REG_FPGA_SUPPORT_3 0x27c ++#define IPU6_BUTTRESS_REG_FPGA_SUPPORT_4 0x280 ++#define IPU6_BUTTRESS_REG_FPGA_SUPPORT_5 0x284 ++#define IPU6_BUTTRESS_REG_FPGA_SUPPORT_6 0x288 ++#define IPU6_BUTTRESS_REG_FPGA_SUPPORT_7 0x28c ++ ++#define BUTTRESS_REG_WDT 0x8 ++#define BUTTRESS_REG_BTRS_CTRL 0xc ++#define BUTTRESS_REG_BTRS_CTRL_STALL_MODE_VC0 BIT(0) ++#define BUTTRESS_REG_BTRS_CTRL_STALL_MODE_VC1 BIT(1) ++#define BUTTRESS_REG_BTRS_CTRL_REF_CLK_IND GENMASK(9, 8) ++ ++#define BUTTRESS_REG_FW_RESET_CTL 0x30 ++#define BUTTRESS_FW_RESET_CTL_START BIT(0) ++#define BUTTRESS_FW_RESET_CTL_DONE BIT(1) ++ ++#define BUTTRESS_REG_IS_FREQ_CTL 0x34 ++#define BUTTRESS_REG_PS_FREQ_CTL 0x38 ++ ++#define BUTTRESS_FREQ_CTL_START BIT(31) ++#define BUTTRESS_FREQ_CTL_ICCMAX_LEVEL GENMASK(19, 16) ++#define BUTTRESS_FREQ_CTL_QOS_FLOOR_MASK GENMASK(15, 8) ++#define BUTTRESS_FREQ_CTL_RATIO_MASK GENMASK(7, 0) ++ ++#define BUTTRESS_REG_PWR_STATE 0x5c ++ ++#define BUTTRESS_PWR_STATE_RESET 0x0 ++#define BUTTRESS_PWR_STATE_PWR_ON_DONE 0x1 ++#define BUTTRESS_PWR_STATE_PWR_RDY 0x3 ++#define BUTTRESS_PWR_STATE_PWR_IDLE 0x4 ++ ++#define BUTTRESS_PWR_STATE_HH_STATUS_MASK GENMASK(12, 11) ++ ++enum { ++ BUTTRESS_PWR_STATE_HH_STATE_IDLE, ++ BUTTRESS_PWR_STATE_HH_STATE_IN_PRGS, ++ BUTTRESS_PWR_STATE_HH_STATE_DONE, ++ BUTTRESS_PWR_STATE_HH_STATE_ERR, ++}; ++ ++#define BUTTRESS_PWR_STATE_IS_PWR_FSM_MASK GENMASK(23, 19) ++ ++#define BUTTRESS_PWR_STATE_IS_PWR_FSM_IDLE 0x0 ++#define BUTTRESS_PWR_STATE_IS_PWR_FSM_WAIT_4_PLL_CMP 0x1 ++#define BUTTRESS_PWR_STATE_IS_PWR_FSM_WAIT_4_CLKACK 0x2 ++#define BUTTRESS_PWR_STATE_IS_PWR_FSM_WAIT_4_PG_ACK 0x3 ++#define BUTTRESS_PWR_STATE_IS_PWR_FSM_RST_ASSRT_CYCLES 0x4 ++#define BUTTRESS_PWR_STATE_IS_PWR_FSM_STOP_CLK_CYCLES1 0x5 ++#define BUTTRESS_PWR_STATE_IS_PWR_FSM_STOP_CLK_CYCLES2 0x6 ++#define BUTTRESS_PWR_STATE_IS_PWR_FSM_RST_DEASSRT_CYCLES 0x7 ++#define BUTTRESS_PWR_STATE_IS_PWR_FSM_WAIT_4_FUSE_WR_CMP 0x8 ++#define BUTTRESS_PWR_STATE_IS_PWR_FSM_BRK_POINT 0x9 ++#define BUTTRESS_PWR_STATE_IS_PWR_FSM_IS_RDY 0xa ++#define BUTTRESS_PWR_STATE_IS_PWR_FSM_HALT_HALTED 0xb ++#define BUTTRESS_PWR_STATE_IS_PWR_FSM_RST_DURATION_CNT3 0xc ++#define BUTTRESS_PWR_STATE_IS_PWR_FSM_WAIT_4_CLKACK_PD 0xd ++#define BUTTRESS_PWR_STATE_IS_PWR_FSM_PD_BRK_POINT 0xe ++#define BUTTRESS_PWR_STATE_IS_PWR_FSM_WAIT_4_PD_PG_ACK0 0xf ++ ++#define BUTTRESS_PWR_STATE_PS_PWR_FSM_MASK GENMASK(28, 24) ++ ++#define BUTTRESS_PWR_STATE_PS_PWR_FSM_IDLE 0x0 ++#define BUTTRESS_PWR_STATE_PS_PWR_FSM_WAIT_PU_PLL_IP_RDY 0x1 ++#define BUTTRESS_PWR_STATE_PS_PWR_FSM_WAIT_RO_PRE_CNT_EXH 0x2 ++#define BUTTRESS_PWR_STATE_PS_PWR_FSM_WAIT_PU_VGI_PWRGOOD 0x3 ++#define BUTTRESS_PWR_STATE_PS_PWR_FSM_WAIT_RO_POST_CNT_EXH 0x4 ++#define BUTTRESS_PWR_STATE_PS_PWR_FSM_WR_PLL_RATIO 0x5 ++#define BUTTRESS_PWR_STATE_PS_PWR_FSM_WAIT_PU_PLL_CMP 0x6 ++#define BUTTRESS_PWR_STATE_PS_PWR_FSM_WAIT_PU_CLKACK 0x7 ++#define BUTTRESS_PWR_STATE_PS_PWR_FSM_RST_ASSRT_CYCLES 0x8 ++#define BUTTRESS_PWR_STATE_PS_PWR_FSM_STOP_CLK_CYCLES1 0x9 ++#define BUTTRESS_PWR_STATE_PS_PWR_FSM_STOP_CLK_CYCLES2 0xa ++#define BUTTRESS_PWR_STATE_PS_PWR_FSM_RST_DEASSRT_CYCLES 0xb ++#define BUTTRESS_PWR_STATE_PS_PWR_FSM_PU_BRK_PNT 0xc ++#define BUTTRESS_PWR_STATE_PS_PWR_FSM_WAIT_FUSE_ACCPT 0xd ++#define BUTTRESS_PWR_STATE_PS_PWR_FSM_PS_PWR_UP 0xf ++#define BUTTRESS_PWR_STATE_PS_PWR_FSM_WAIT_4_HALTED 0x10 ++#define BUTTRESS_PWR_STATE_PS_PWR_FSM_RESET_CNT3 0x11 ++#define BUTTRESS_PWR_STATE_PS_PWR_FSM_WAIT_PD_CLKACK 0x12 ++#define BUTTRESS_PWR_STATE_PS_PWR_FSM_WAIT_PD_OFF_IND 0x13 ++#define BUTTRESS_PWR_STATE_PS_PWR_FSM_WAIT_DVFS_PH4 0x14 ++#define BUTTRESS_PWR_STATE_PS_PWR_FSM_WAIT_DVFS_PLL_CMP 0x15 ++#define BUTTRESS_PWR_STATE_PS_PWR_FSM_WAIT_DVFS_CLKACK 0x16 ++ ++#define BUTTRESS_REG_SECURITY_CTL 0x300 ++#define BUTTRESS_REG_SKU 0x314 ++#define BUTTRESS_REG_SECURITY_TOUCH 0x318 ++#define BUTTRESS_REG_CAMERA_MASK 0x84 ++ ++#define BUTTRESS_SECURITY_CTL_FW_SECURE_MODE BIT(16) ++#define BUTTRESS_SECURITY_CTL_FW_SETUP_MASK GENMASK(4, 0) ++ ++#define BUTTRESS_SECURITY_CTL_FW_SETUP_DONE BIT(0) ++#define BUTTRESS_SECURITY_CTL_AUTH_DONE BIT(1) ++#define BUTTRESS_SECURITY_CTL_AUTH_FAILED BIT(3) ++ ++#define BUTTRESS_REG_FW_SOURCE_BASE_LO 0x78 ++#define BUTTRESS_REG_FW_SOURCE_BASE_HI 0x7C ++#define BUTTRESS_REG_FW_SOURCE_SIZE 0x80 ++ ++#define BUTTRESS_REG_ISR_STATUS 0x90 ++#define BUTTRESS_REG_ISR_ENABLED_STATUS 0x94 ++#define BUTTRESS_REG_ISR_ENABLE 0x98 ++#define BUTTRESS_REG_ISR_CLEAR 0x9C ++ ++#define BUTTRESS_ISR_IS_IRQ BIT(0) ++#define BUTTRESS_ISR_PS_IRQ BIT(1) ++#define BUTTRESS_ISR_IPC_EXEC_DONE_BY_CSE BIT(2) ++#define BUTTRESS_ISR_IPC_EXEC_DONE_BY_ISH BIT(3) ++#define BUTTRESS_ISR_IPC_FROM_CSE_IS_WAITING BIT(4) ++#define BUTTRESS_ISR_IPC_FROM_ISH_IS_WAITING BIT(5) ++#define BUTTRESS_ISR_CSE_CSR_SET BIT(6) ++#define BUTTRESS_ISR_ISH_CSR_SET BIT(7) ++#define BUTTRESS_ISR_SPURIOUS_CMP BIT(8) ++#define BUTTRESS_ISR_WATCHDOG_EXPIRED BIT(9) ++#define BUTTRESS_ISR_PUNIT_2_IUNIT_IRQ BIT(10) ++#define BUTTRESS_ISR_SAI_VIOLATION BIT(11) ++#define BUTTRESS_ISR_HW_ASSERTION BIT(12) ++#define BUTTRESS_ISR_IS_CORRECTABLE_MEM_ERR BIT(13) ++#define BUTTRESS_ISR_IS_FATAL_MEM_ERR BIT(14) ++#define BUTTRESS_ISR_IS_NON_FATAL_MEM_ERR BIT(15) ++#define BUTTRESS_ISR_PS_CORRECTABLE_MEM_ERR BIT(16) ++#define BUTTRESS_ISR_PS_FATAL_MEM_ERR BIT(17) ++#define BUTTRESS_ISR_PS_NON_FATAL_MEM_ERR BIT(18) ++#define BUTTRESS_ISR_PS_FAST_THROTTLE BIT(19) ++#define BUTTRESS_ISR_UFI_ERROR BIT(20) ++ ++#define BUTTRESS_REG_IU2CSEDB0 0x100 ++ ++#define BUTTRESS_IU2CSEDB0_BUSY BIT(31) ++#define BUTTRESS_IU2CSEDB0_IPC_CLIENT_ID_VAL 2 ++ ++#define BUTTRESS_REG_IU2CSEDATA0 0x104 ++ ++#define BUTTRESS_IU2CSEDATA0_IPC_BOOT_LOAD 1 ++#define BUTTRESS_IU2CSEDATA0_IPC_AUTH_RUN 2 ++#define BUTTRESS_IU2CSEDATA0_IPC_AUTH_REPLACE 3 ++#define BUTTRESS_IU2CSEDATA0_IPC_UPDATE_SECURE_TOUCH 16 ++ ++#define BUTTRESS_CSE2IUDATA0_IPC_BOOT_LOAD_DONE BIT(0) ++#define BUTTRESS_CSE2IUDATA0_IPC_AUTH_RUN_DONE BIT(1) ++#define BUTTRESS_CSE2IUDATA0_IPC_AUTH_REPLACE_DONE BIT(2) ++#define BUTTRESS_CSE2IUDATA0_IPC_UPDATE_SECURE_TOUCH_DONE BIT(4) ++ ++#define BUTTRESS_REG_IU2CSECSR 0x108 ++ ++#define BUTTRESS_IU2CSECSR_IPC_PEER_COMP_ACTIONS_RST_PHASE1 BIT(0) ++#define BUTTRESS_IU2CSECSR_IPC_PEER_COMP_ACTIONS_RST_PHASE2 BIT(1) ++#define BUTTRESS_IU2CSECSR_IPC_PEER_QUERIED_IP_COMP_ACTIONS_RST_PHASE BIT(2) ++#define BUTTRESS_IU2CSECSR_IPC_PEER_ASSERTED_REG_VALID_REQ BIT(3) ++#define BUTTRESS_IU2CSECSR_IPC_PEER_ACKED_REG_VALID BIT(4) ++#define BUTTRESS_IU2CSECSR_IPC_PEER_DEASSERTED_REG_VALID_REQ BIT(5) ++ ++#define BUTTRESS_REG_CSE2IUDB0 0x304 ++#define BUTTRESS_REG_CSE2IUCSR 0x30C ++#define BUTTRESS_REG_CSE2IUDATA0 0x308 ++ ++/* 0x20 == NACK, 0xf == unknown command */ ++#define BUTTRESS_CSE2IUDATA0_IPC_NACK 0xf20 ++#define BUTTRESS_CSE2IUDATA0_IPC_NACK_MASK GENMASK(15, 0) ++ ++#define BUTTRESS_REG_ISH2IUCSR 0x50 ++#define BUTTRESS_REG_ISH2IUDB0 0x54 ++#define BUTTRESS_REG_ISH2IUDATA0 0x58 ++ ++#define BUTTRESS_REG_IU2ISHDB0 0x10C ++#define BUTTRESS_REG_IU2ISHDATA0 0x110 ++#define BUTTRESS_REG_IU2ISHDATA1 0x114 ++#define BUTTRESS_REG_IU2ISHCSR 0x118 ++ ++#define BUTTRESS_REG_FABRIC_CMD 0x88 ++ ++#define BUTTRESS_FABRIC_CMD_START_TSC_SYNC BIT(0) ++#define BUTTRESS_FABRIC_CMD_IS_DRAIN BIT(4) ++ ++#define BUTTRESS_REG_TSW_CTL 0x120 ++#define BUTTRESS_TSW_CTL_SOFT_RESET BIT(8) ++ ++#define BUTTRESS_REG_TSC_LO 0x164 ++#define BUTTRESS_REG_TSC_HI 0x168 ++ ++#define BUTTRESS_IRQS (BUTTRESS_ISR_IPC_FROM_CSE_IS_WAITING | \ ++ BUTTRESS_ISR_IPC_EXEC_DONE_BY_CSE | \ ++ BUTTRESS_ISR_IS_IRQ | BUTTRESS_ISR_PS_IRQ) ++ ++#define BUTTRESS_EVENT (BUTTRESS_ISR_IPC_FROM_CSE_IS_WAITING | \ ++ BUTTRESS_ISR_IPC_FROM_ISH_IS_WAITING | \ ++ BUTTRESS_ISR_IPC_EXEC_DONE_BY_CSE | \ ++ BUTTRESS_ISR_IPC_EXEC_DONE_BY_ISH | \ ++ BUTTRESS_ISR_SAI_VIOLATION) ++#endif /* IPU6_PLATFORM_BUTTRESS_REGS_H */ +-- +2.43.0 + +From 4e01328e611be4ba5a2c9fe2baedfeb239db9d99 Mon Sep 17 00:00:00 2001 +From: Bingbu Cao <bingbu.cao@intel.com> +Date: Thu, 11 Jan 2024 14:55:18 +0800 +Subject: [PATCH 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 <bingbu.cao@intel.com> +Link: https://lore.kernel.org/r/20240111065531.2418836-5-bingbu.cao@intel.com +Signed-off-by: Hans de Goede <hdegoede@redhat.com> +--- + drivers/media/pci/intel/ipu6/ipu6-cpd.c | 362 ++++++++++++++++++++++++ + drivers/media/pci/intel/ipu6/ipu6-cpd.h | 105 +++++++ + 2 files changed, 467 insertions(+) + create mode 100644 drivers/media/pci/intel/ipu6/ipu6-cpd.c + create mode 100644 drivers/media/pci/intel/ipu6/ipu6-cpd.h + +diff --git a/drivers/media/pci/intel/ipu6/ipu6-cpd.c b/drivers/media/pci/intel/ipu6/ipu6-cpd.c +new file mode 100644 +index 000000000000..b0ffd04c4cd3 +--- /dev/null ++++ b/drivers/media/pci/intel/ipu6/ipu6-cpd.c +@@ -0,0 +1,362 @@ ++// SPDX-License-Identifier: GPL-2.0-only ++/* ++ * Copyright (C) 2013 - 2023 Intel Corporation ++ */ ++ ++#include <linux/bitfield.h> ++#include <linux/bits.h> ++#include <linux/err.h> ++#include <linux/dma-mapping.h> ++#include <linux/gfp_types.h> ++#include <linux/math64.h> ++#include <linux/sizes.h> ++#include <linux/types.h> ++ ++#include "ipu6.h" ++#include "ipu6-bus.h" ++#include "ipu6-cpd.h" ++ ++/* 15 entries + header*/ ++#define MAX_PKG_DIR_ENT_CNT 16 ++/* 2 qword per entry/header */ ++#define PKG_DIR_ENT_LEN 2 ++/* PKG_DIR size in bytes */ ++#define PKG_DIR_SIZE ((MAX_PKG_DIR_ENT_CNT) * \ ++ (PKG_DIR_ENT_LEN) * sizeof(u64)) ++/* _IUPKDR_ */ ++#define PKG_DIR_HDR_MARK 0x5f4955504b44525fULL ++ ++/* $CPD */ ++#define CPD_HDR_MARK 0x44504324 ++ ++#define MAX_MANIFEST_SIZE (SZ_2K * sizeof(u32)) ++#define MAX_METADATA_SIZE SZ_64K ++ ++#define MAX_COMPONENT_ID 127 ++#define MAX_COMPONENT_VERSION 0xffff ++ ++#define MANIFEST_IDX 0 ++#define METADATA_IDX 1 ++#define MODULEDATA_IDX 2 ++/* ++ * PKG_DIR Entry (type == id) ++ * 63:56 55 54:48 47:32 31:24 23:0 ++ * Rsvd Rsvd Type Version Rsvd Size ++ */ ++#define PKG_DIR_SIZE_MASK GENMASK(23, 0) ++#define PKG_DIR_VERSION_MASK GENMASK(47, 32) ++#define PKG_DIR_TYPE_MASK GENMASK(54, 48) ++ ++static inline const struct ipu6_cpd_ent *ipu6_cpd_get_entry(const void *cpd, ++ u8 idx) ++{ ++ const struct ipu6_cpd_hdr *cpd_hdr = cpd; ++ const struct ipu6_cpd_ent *ent; ++ ++ ent = (const struct ipu6_cpd_ent *)((const u8 *)cpd + cpd_hdr->hdr_len); ++ return ent + idx; ++} ++ ++#define ipu6_cpd_get_manifest(cpd) ipu6_cpd_get_entry(cpd, MANIFEST_IDX) ++#define ipu6_cpd_get_metadata(cpd) ipu6_cpd_get_entry(cpd, METADATA_IDX) ++#define ipu6_cpd_get_moduledata(cpd) ipu6_cpd_get_entry(cpd, MODULEDATA_IDX) ++ ++static const struct ipu6_cpd_metadata_cmpnt_hdr * ++ipu6_cpd_metadata_get_cmpnt(struct ipu6_device *isp, const void *metadata, ++ unsigned int metadata_size, u8 idx) ++{ ++ size_t extn_size = sizeof(struct ipu6_cpd_metadata_extn); ++ size_t cmpnt_count = metadata_size - extn_size; ++ ++ cmpnt_count = div_u64(cmpnt_count, isp->cpd_metadata_cmpnt_size); ++ ++ if (idx > MAX_COMPONENT_ID || idx >= cmpnt_count) { ++ dev_err(&isp->pdev->dev, "Component index out of range (%d)\n", ++ idx); ++ return ERR_PTR(-EINVAL); ++ } ++ ++ return metadata + extn_size + idx * isp->cpd_metadata_cmpnt_size; ++} ++ ++static u32 ipu6_cpd_metadata_cmpnt_version(struct ipu6_device *isp, ++ const void *metadata, ++ unsigned int metadata_size, u8 idx) ++{ ++ const struct ipu6_cpd_metadata_cmpnt_hdr *cmpnt; ++ ++ cmpnt = ipu6_cpd_metadata_get_cmpnt(isp, metadata, metadata_size, idx); ++ if (IS_ERR(cmpnt)) ++ return PTR_ERR(cmpnt); ++ ++ return cmpnt->ver; ++} ++ ++static int ipu6_cpd_metadata_get_cmpnt_id(struct ipu6_device *isp, ++ const void *metadata, ++ unsigned int metadata_size, u8 idx) ++{ ++ const struct ipu6_cpd_metadata_cmpnt_hdr *cmpnt; ++ ++ cmpnt = ipu6_cpd_metadata_get_cmpnt(isp, metadata, metadata_size, idx); ++ if (IS_ERR(cmpnt)) ++ return PTR_ERR(cmpnt); ++ ++ return cmpnt->id; ++} ++ ++static int ipu6_cpd_parse_module_data(struct ipu6_device *isp, ++ const void *module_data, ++ unsigned int module_data_size, ++ dma_addr_t dma_addr_module_data, ++ u64 *pkg_dir, const void *metadata, ++ unsigned int metadata_size) ++{ ++ const struct ipu6_cpd_module_data_hdr *module_data_hdr; ++ const struct ipu6_cpd_hdr *dir_hdr; ++ const struct ipu6_cpd_ent *dir_ent; ++ unsigned int i; ++ u8 len; ++ ++ if (!module_data) ++ return -EINVAL; ++ ++ module_data_hdr = module_data; ++ dir_hdr = module_data + module_data_hdr->hdr_len; ++ len = dir_hdr->hdr_len; ++ dir_ent = (const struct ipu6_cpd_ent *)(((u8 *)dir_hdr) + len); ++ ++ pkg_dir[0] = PKG_DIR_HDR_MARK; ++ /* pkg_dir entry count = component count + pkg_dir header */ ++ pkg_dir[1] = dir_hdr->ent_cnt + 1; ++ ++ for (i = 0; i < dir_hdr->ent_cnt; i++, dir_ent++) { ++ u64 *p = &pkg_dir[PKG_DIR_ENT_LEN * (1 + i)]; ++ int ver, id; ++ ++ *p++ = dma_addr_module_data + dir_ent->offset; ++ id = ipu6_cpd_metadata_get_cmpnt_id(isp, metadata, ++ metadata_size, i); ++ if (id < 0 || id > MAX_COMPONENT_ID) { ++ dev_err(&isp->pdev->dev, "Invalid CPD component id\n"); ++ return -EINVAL; ++ } ++ ++ ver = ipu6_cpd_metadata_cmpnt_version(isp, metadata, ++ metadata_size, i); ++ if (ver < 0 || ver > MAX_COMPONENT_VERSION) { ++ dev_err(&isp->pdev->dev, ++ "Invalid CPD component version\n"); ++ return -EINVAL; ++ } ++ ++ *p = FIELD_PREP(PKG_DIR_SIZE_MASK, dir_ent->len) | ++ FIELD_PREP(PKG_DIR_TYPE_MASK, id) | ++ FIELD_PREP(PKG_DIR_VERSION_MASK, ver); ++ } ++ ++ return 0; ++} ++ ++int ipu6_cpd_create_pkg_dir(struct ipu6_bus_device *adev, const void *src) ++{ ++ dma_addr_t dma_addr_src = sg_dma_address(adev->fw_sgt.sgl); ++ const struct ipu6_cpd_ent *ent, *man_ent, *met_ent; ++ struct device *dev = &adev->auxdev.dev; ++ struct ipu6_device *isp = adev->isp; ++ unsigned int man_sz, met_sz; ++ void *pkg_dir_pos; ++ int ret; ++ ++ man_ent = ipu6_cpd_get_manifest(src); ++ man_sz = man_ent->len; ++ ++ met_ent = ipu6_cpd_get_metadata(src); ++ met_sz = met_ent->len; ++ ++ adev->pkg_dir_size = PKG_DIR_SIZE + man_sz + met_sz; ++ adev->pkg_dir = dma_alloc_attrs(dev, adev->pkg_dir_size, ++ &adev->pkg_dir_dma_addr, GFP_KERNEL, 0); ++ if (!adev->pkg_dir) ++ return -ENOMEM; ++ ++ /* ++ * pkg_dir entry/header: ++ * qword | 63:56 | 55 | 54:48 | 47:32 | 31:24 | 23:0 ++ * N Address/Offset/"_IUPKDR_" ++ * N + 1 | rsvd | rsvd | type | ver | rsvd | size ++ * ++ * We can ignore other fields that size in N + 1 qword as they ++ * are 0 anyway. Just setting size for now. ++ */ ++ ++ ent = ipu6_cpd_get_moduledata(src); ++ ++ ret = ipu6_cpd_parse_module_data(isp, src + ent->offset, ++ ent->len, dma_addr_src + ent->offset, ++ adev->pkg_dir, src + met_ent->offset, ++ met_ent->len); ++ if (ret) { ++ dev_err(&isp->pdev->dev, "Failed to parse module data\n"); ++ dma_free_attrs(dev, adev->pkg_dir_size, ++ adev->pkg_dir, adev->pkg_dir_dma_addr, 0); ++ return ret; ++ } ++ ++ /* Copy manifest after pkg_dir */ ++ pkg_dir_pos = adev->pkg_dir + PKG_DIR_ENT_LEN * MAX_PKG_DIR_ENT_CNT; ++ memcpy(pkg_dir_pos, src + man_ent->offset, man_sz); ++ ++ /* Copy metadata after manifest */ ++ pkg_dir_pos += man_sz; ++ memcpy(pkg_dir_pos, src + met_ent->offset, met_sz); ++ ++ dma_sync_single_range_for_device(dev, adev->pkg_dir_dma_addr, ++ 0, adev->pkg_dir_size, DMA_TO_DEVICE); ++ ++ return 0; ++} ++EXPORT_SYMBOL_NS_GPL(ipu6_cpd_create_pkg_dir, INTEL_IPU6); ++ ++void ipu6_cpd_free_pkg_dir(struct ipu6_bus_device *adev) ++{ ++ dma_free_attrs(&adev->auxdev.dev, adev->pkg_dir_size, adev->pkg_dir, ++ adev->pkg_dir_dma_addr, 0); ++} ++EXPORT_SYMBOL_NS_GPL(ipu6_cpd_free_pkg_dir, INTEL_IPU6); ++ ++static int ipu6_cpd_validate_cpd(struct ipu6_device *isp, const void *cpd, ++ unsigned long cpd_size, ++ unsigned long data_size) ++{ ++ const struct ipu6_cpd_hdr *cpd_hdr = cpd; ++ const struct ipu6_cpd_ent *ent; ++ unsigned int i; ++ u8 len; ++ ++ len = cpd_hdr->hdr_len; ++ ++ /* Ensure cpd hdr is within moduledata */ ++ if (cpd_size < len) { ++ dev_err(&isp->pdev->dev, "Invalid CPD moduledata size\n"); ++ return -EINVAL; ++ } ++ ++ /* Sanity check for CPD header */ ++ if ((cpd_size - len) / sizeof(*ent) < cpd_hdr->ent_cnt) { ++ dev_err(&isp->pdev->dev, "Invalid CPD header\n"); ++ return -EINVAL; ++ } ++ ++ /* Ensure that all entries are within moduledata */ ++ ent = (const struct ipu6_cpd_ent *)(((const u8 *)cpd_hdr) + len); ++ for (i = 0; i < cpd_hdr->ent_cnt; i++, ent++) { ++ if (data_size < ent->offset || ++ data_size - ent->offset < ent->len) { ++ dev_err(&isp->pdev->dev, "Invalid CPD entry (%d)\n", i); ++ return -EINVAL; ++ } ++ } ++ ++ return 0; ++} ++ ++static int ipu6_cpd_validate_moduledata(struct ipu6_device *isp, ++ const void *moduledata, ++ u32 moduledata_size) ++{ ++ const struct ipu6_cpd_module_data_hdr *mod_hdr = moduledata; ++ int ret; ++ ++ /* Ensure moduledata hdr is within moduledata */ ++ if (moduledata_size < sizeof(*mod_hdr) || ++ moduledata_size < mod_hdr->hdr_len) { ++ dev_err(&isp->pdev->dev, "Invalid CPD moduledata size\n"); ++ return -EINVAL; ++ } ++ ++ dev_info(&isp->pdev->dev, "FW version: %x\n", mod_hdr->fw_pkg_date); ++ ret = ipu6_cpd_validate_cpd(isp, moduledata + mod_hdr->hdr_len, ++ moduledata_size - mod_hdr->hdr_len, ++ moduledata_size); ++ if (ret) { ++ dev_err(&isp->pdev->dev, "Invalid CPD in moduledata\n"); ++ return ret; ++ } ++ ++ return 0; ++} ++ ++static int ipu6_cpd_validate_metadata(struct ipu6_device *isp, ++ const void *metadata, u32 meta_size) ++{ ++ const struct ipu6_cpd_metadata_extn *extn = metadata; ++ ++ /* Sanity check for metadata size */ ++ if (meta_size < sizeof(*extn) || meta_size > MAX_METADATA_SIZE) { ++ dev_err(&isp->pdev->dev, "Invalid CPD metadata\n"); ++ return -EINVAL; ++ } ++ ++ /* Validate extension and image types */ ++ if (extn->extn_type != IPU6_CPD_METADATA_EXTN_TYPE_IUNIT || ++ extn->img_type != IPU6_CPD_METADATA_IMAGE_TYPE_MAIN_FIRMWARE) { ++ dev_err(&isp->pdev->dev, ++ "Invalid CPD metadata descriptor img_type (%d)\n", ++ extn->img_type); ++ return -EINVAL; ++ } ++ ++ /* Validate metadata size multiple of metadata components */ ++ if ((meta_size - sizeof(*extn)) % isp->cpd_metadata_cmpnt_size) { ++ dev_err(&isp->pdev->dev, "Invalid CPD metadata size\n"); ++ return -EINVAL; ++ } ++ ++ return 0; ++} ++ ++int ipu6_cpd_validate_cpd_file(struct ipu6_device *isp, const void *cpd_file, ++ unsigned long cpd_file_size) ++{ ++ const struct ipu6_cpd_hdr *hdr = cpd_file; ++ const struct ipu6_cpd_ent *ent; ++ int ret; ++ ++ ret = ipu6_cpd_validate_cpd(isp, cpd_file, cpd_file_size, ++ cpd_file_size); ++ if (ret) { ++ dev_err(&isp->pdev->dev, "Invalid CPD in file\n"); ++ return ret; ++ } ++ ++ /* Check for CPD file marker */ ++ if (hdr->hdr_mark != CPD_HDR_MARK) { ++ dev_err(&isp->pdev->dev, "Invalid CPD header\n"); ++ return -EINVAL; ++ } ++ ++ /* Sanity check for manifest size */ ++ ent = ipu6_cpd_get_manifest(cpd_file); ++ if (ent->len > MAX_MANIFEST_SIZE) { ++ dev_err(&isp->pdev->dev, "Invalid CPD manifest size\n"); ++ return -EINVAL; ++ } ++ ++ /* Validate metadata */ ++ ent = ipu6_cpd_get_metadata(cpd_file); ++ ret = ipu6_cpd_validate_metadata(isp, cpd_file + ent->offset, ent->len); ++ if (ret) { ++ dev_err(&isp->pdev->dev, "Invalid CPD metadata\n"); ++ return ret; ++ } ++ ++ /* Validate moduledata */ ++ ent = ipu6_cpd_get_moduledata(cpd_file); ++ ret = ipu6_cpd_validate_moduledata(isp, cpd_file + ent->offset, ++ ent->len); ++ if (ret) ++ dev_err(&isp->pdev->dev, "Invalid CPD moduledata\n"); ++ ++ return ret; ++} +diff --git a/drivers/media/pci/intel/ipu6/ipu6-cpd.h b/drivers/media/pci/intel/ipu6/ipu6-cpd.h +new file mode 100644 +index 000000000000..37465d507386 +--- /dev/null ++++ b/drivers/media/pci/intel/ipu6/ipu6-cpd.h +@@ -0,0 +1,105 @@ ++/* SPDX-License-Identifier: GPL-2.0-only */ ++/* Copyright (C) 2015 - 2023 Intel Corporation */ ++ ++#ifndef IPU6_CPD_H ++#define IPU6_CPD_H ++ ++struct ipu6_device; ++struct ipu6_bus_device; ++ ++#define IPU6_CPD_SIZE_OF_FW_ARCH_VERSION 7 ++#define IPU6_CPD_SIZE_OF_SYSTEM_VERSION 11 ++#define IPU6_CPD_SIZE_OF_COMPONENT_NAME 12 ++ ++#define IPU6_CPD_METADATA_EXTN_TYPE_IUNIT 0x10 ++ ++#define IPU6_CPD_METADATA_IMAGE_TYPE_RESERVED 0 ++#define IPU6_CPD_METADATA_IMAGE_TYPE_BOOTLOADER 1 ++#define IPU6_CPD_METADATA_IMAGE_TYPE_MAIN_FIRMWARE 2 ++ ++#define IPU6_CPD_PKG_DIR_PSYS_SERVER_IDX 0 ++#define IPU6_CPD_PKG_DIR_ISYS_SERVER_IDX 1 ++ ++#define IPU6_CPD_PKG_DIR_CLIENT_PG_TYPE 3 ++ ++#define IPU6_CPD_METADATA_HASH_KEY_SIZE 48 ++#define IPU6SE_CPD_METADATA_HASH_KEY_SIZE 32 ++ ++struct ipu6_cpd_module_data_hdr { ++ u32 hdr_len; ++ u32 endian; ++ u32 fw_pkg_date; ++ u32 hive_sdk_date; ++ u32 compiler_date; ++ u32 target_platform_type; ++ u8 sys_ver[IPU6_CPD_SIZE_OF_SYSTEM_VERSION]; ++ u8 fw_arch_ver[IPU6_CPD_SIZE_OF_FW_ARCH_VERSION]; ++ u8 rsvd[2]; ++} __packed; ++ ++/* ++ * ipu6_cpd_hdr structure updated as the chksum and ++ * sub_partition_name is unused on host side ++ * CSE layout version 1.6 for IPU6SE (hdr_len = 0x10) ++ * CSE layout version 1.7 for IPU6 (hdr_len = 0x14) ++ */ ++struct ipu6_cpd_hdr { ++ u32 hdr_mark; ++ u32 ent_cnt; ++ u8 hdr_ver; ++ u8 ent_ver; ++ u8 hdr_len; ++} __packed; ++ ++struct ipu6_cpd_ent { ++ u8 name[IPU6_CPD_SIZE_OF_COMPONENT_NAME]; ++ u32 offset; ++ u32 len; ++ u8 rsvd[4]; ++} __packed; ++ ++struct ipu6_cpd_metadata_cmpnt_hdr { ++ u32 id; ++ u32 size; ++ u32 ver; ++} __packed; ++ ++struct ipu6_cpd_metadata_cmpnt { ++ struct ipu6_cpd_metadata_cmpnt_hdr hdr; ++ u8 sha2_hash[IPU6_CPD_METADATA_HASH_KEY_SIZE]; ++ u32 entry_point; ++ u32 icache_base_offs; ++ u8 attrs[16]; ++} __packed; ++ ++struct ipu6se_cpd_metadata_cmpnt { ++ struct ipu6_cpd_metadata_cmpnt_hdr hdr; ++ u8 sha2_hash[IPU6SE_CPD_METADATA_HASH_KEY_SIZE]; ++ u32 entry_point; ++ u32 icache_base_offs; ++ u8 attrs[16]; ++} __packed; ++ ++struct ipu6_cpd_metadata_extn { ++ u32 extn_type; ++ u32 len; ++ u32 img_type; ++ u8 rsvd[16]; ++} __packed; ++ ++struct ipu6_cpd_client_pkg_hdr { ++ u32 prog_list_offs; ++ u32 prog_list_size; ++ u32 prog_desc_offs; ++ u32 prog_desc_size; ++ u32 pg_manifest_offs; ++ u32 pg_manifest_size; ++ u32 prog_bin_offs; ++ u32 prog_bin_size; ++} __packed; ++ ++int ipu6_cpd_create_pkg_dir(struct ipu6_bus_device *adev, const void *src); ++void ipu6_cpd_free_pkg_dir(struct ipu6_bus_device *adev); ++int ipu6_cpd_validate_cpd_file(struct ipu6_device *isp, const void *cpd_file, ++ unsigned long cpd_file_size); ++#endif /* IPU6_CPD_H */ +-- +2.43.0 + +From 05c36b7a6306ed7ef15b9fd6039920a7d08626cc Mon Sep 17 00:00:00 2001 +From: Bingbu Cao <bingbu.cao@intel.com> +Date: Thu, 11 Jan 2024 14:55:19 +0800 +Subject: [PATCH 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 <bingbu.cao@intel.com> +Link: https://lore.kernel.org/r/20240111065531.2418836-6-bingbu.cao@intel.com +Signed-off-by: Hans de Goede <hdegoede@redhat.com> +--- + drivers/media/pci/intel/ipu6/ipu6-dma.c | 502 ++++++++++++++ + drivers/media/pci/intel/ipu6/ipu6-dma.h | 19 + + drivers/media/pci/intel/ipu6/ipu6-mmu.c | 845 ++++++++++++++++++++++++ + drivers/media/pci/intel/ipu6/ipu6-mmu.h | 73 ++ + 4 files changed, 1439 insertions(+) + create mode 100644 drivers/media/pci/intel/ipu6/ipu6-dma.c + create mode 100644 drivers/media/pci/intel/ipu6/ipu6-dma.h + create mode 100644 drivers/media/pci/intel/ipu6/ipu6-mmu.c + create mode 100644 drivers/media/pci/intel/ipu6/ipu6-mmu.h + +diff --git a/drivers/media/pci/intel/ipu6/ipu6-dma.c b/drivers/media/pci/intel/ipu6/ipu6-dma.c +new file mode 100644 +index 000000000000..3d77c6e5a45e +--- /dev/null ++++ b/drivers/media/pci/intel/ipu6/ipu6-dma.c +@@ -0,0 +1,502 @@ ++// SPDX-License-Identifier: GPL-2.0-only ++/* ++ * Copyright (C) 2013 - 2023 Intel Corporation ++ */ ++ ++#include <linux/cacheflush.h> ++#include <linux/dma-mapping.h> ++#include <linux/iova.h> ++#include <linux/list.h> ++#include <linux/mm.h> ++#include <linux/vmalloc.h> ++#include <linux/scatterlist.h> ++#include <linux/slab.h> ++#include <linux/types.h> ++ ++#include "ipu6.h" ++#include "ipu6-bus.h" ++#include "ipu6-dma.h" ++#include "ipu6-mmu.h" ++ ++struct vm_info { ++ struct list_head list; ++ struct page **pages; ++ dma_addr_t ipu6_iova; ++ void *vaddr; ++ unsigned long size; ++}; ++ ++static struct vm_info *get_vm_info(struct ipu6_mmu *mmu, dma_addr_t iova) ++{ ++ struct vm_info *info, *save; ++ ++ list_for_each_entry_safe(info, save, &mmu->vma_list, list) { ++ if (iova >= info->ipu6_iova && ++ iova < (info->ipu6_iova + info->size)) ++ return info; ++ } ++ ++ return NULL; ++} ++ ++static void __dma_clear_buffer(struct page *page, size_t size, ++ unsigned long attrs) ++{ ++ void *ptr; ++ ++ if (!page) ++ return; ++ /* ++ * Ensure that the allocated pages are zeroed, and that any data ++ * lurking in the kernel direct-mapped region is invalidated. ++ */ ++ ptr = page_address(page); ++ memset(ptr, 0, size); ++ if ((attrs & DMA_ATTR_SKIP_CPU_SYNC) == 0) ++ clflush_cache_range(ptr, size); ++} ++ ++static struct page **__dma_alloc_buffer(struct device *dev, size_t size, ++ gfp_t gfp, unsigned long attrs) ++{ ++ int count = PHYS_PFN(size); ++ int array_size = count * sizeof(struct page *); ++ struct page **pages; ++ int i = 0; ++ ++ pages = kvzalloc(array_size, GFP_KERNEL); ++ if (!pages) ++ return NULL; ++ ++ gfp |= __GFP_NOWARN; ++ ++ while (count) { ++ int j, order = __fls(count); ++ ++ pages[i] = alloc_pages(gfp, order); ++ while (!pages[i] && order) ++ pages[i] = alloc_pages(gfp, --order); ++ if (!pages[i]) ++ goto error; ++ ++ if (order) { ++ split_page(pages[i], order); ++ j = 1 << order; ++ while (j--) ++ pages[i + j] = pages[i] + j; ++ } ++ ++ __dma_clear_buffer(pages[i], PAGE_SIZE << order, attrs); ++ i += 1 << order; ++ count -= 1 << order; ++ } ++ ++ return pages; ++error: ++ while (i--) ++ if (pages[i]) ++ __free_pages(pages[i], 0); ++ kvfree(pages); ++ return NULL; ++} ++ ++static void __dma_free_buffer(struct device *dev, struct page **pages, ++ size_t size, unsigned long attrs) ++{ ++ int count = PHYS_PFN(size); ++ unsigned int i; ++ ++ for (i = 0; i < count && pages[i]; i++) { ++ __dma_clear_buffer(pages[i], PAGE_SIZE, attrs); ++ __free_pages(pages[i], 0); ++ } ++ ++ kvfree(pages); ++} ++ ++static void ipu6_dma_sync_single_for_cpu(struct device *dev, ++ dma_addr_t dma_handle, ++ size_t size, ++ enum dma_data_direction dir) ++{ ++ void *vaddr; ++ u32 offset; ++ struct vm_info *info; ++ struct ipu6_mmu *mmu = to_ipu6_bus_device(dev)->mmu; ++ ++ info = get_vm_info(mmu, dma_handle); ++ if (WARN_ON(!info)) ++ return; ++ ++ offset = dma_handle - info->ipu6_iova; ++ if (WARN_ON(size > (info->size - offset))) ++ return; ++ ++ vaddr = info->vaddr + offset; ++ clflush_cache_range(vaddr, size); ++} ++ ++static void ipu6_dma_sync_sg_for_cpu(struct device *dev, ++ struct scatterlist *sglist, ++ int nents, enum dma_data_direction dir) ++{ ++ struct scatterlist *sg; ++ int i; ++ ++ for_each_sg(sglist, sg, nents, i) ++ clflush_cache_range(page_to_virt(sg_page(sg)), sg->length); ++} ++ ++static void *ipu6_dma_alloc(struct device *dev, size_t size, ++ dma_addr_t *dma_handle, gfp_t gfp, ++ unsigned long attrs) ++{ ++ struct ipu6_mmu *mmu = to_ipu6_bus_device(dev)->mmu; ++ struct pci_dev *pdev = to_ipu6_bus_device(dev)->isp->pdev; ++ dma_addr_t pci_dma_addr, ipu6_iova; ++ struct vm_info *info; ++ unsigned long count; ++ struct page **pages; ++ struct iova *iova; ++ unsigned int i; ++ int ret; ++ ++ info = kzalloc(sizeof(*info), GFP_KERNEL); ++ if (!info) ++ return NULL; ++ ++ size = PAGE_ALIGN(size); ++ count = PHYS_PFN(size); ++ ++ iova = alloc_iova(&mmu->dmap->iovad, count, ++ PHYS_PFN(dma_get_mask(dev)), 0); ++ if (!iova) ++ goto out_kfree; ++ ++ pages = __dma_alloc_buffer(dev, size, gfp, attrs); ++ if (!pages) ++ goto out_free_iova; ++ ++ dev_dbg(dev, "dma_alloc: size %zu iova low pfn %lu, high pfn %lu\n", ++ size, iova->pfn_lo, iova->pfn_hi); ++ for (i = 0; iova->pfn_lo + i <= iova->pfn_hi; i++) { ++ pci_dma_addr = dma_map_page_attrs(&pdev->dev, pages[i], 0, ++ PAGE_SIZE, DMA_BIDIRECTIONAL, ++ attrs); ++ dev_dbg(dev, "dma_alloc: mapped pci_dma_addr %pad\n", ++ &pci_dma_addr); ++ if (dma_mapping_error(&pdev->dev, pci_dma_addr)) { ++ dev_err(dev, "pci_dma_mapping for page[%d] failed", i); ++ goto out_unmap; ++ } ++ ++ ret = ipu6_mmu_map(mmu->dmap->mmu_info, ++ PFN_PHYS(iova->pfn_lo + i), pci_dma_addr, ++ PAGE_SIZE); ++ if (ret) { ++ dev_err(dev, "ipu6_mmu_map for pci_dma[%d] %pad failed", ++ i, &pci_dma_addr); ++ dma_unmap_page_attrs(&pdev->dev, pci_dma_addr, ++ PAGE_SIZE, DMA_BIDIRECTIONAL, ++ attrs); ++ goto out_unmap; ++ } ++ } ++ ++ info->vaddr = vmap(pages, count, VM_USERMAP, PAGE_KERNEL); ++ if (!info->vaddr) ++ goto out_unmap; ++ ++ *dma_handle = PFN_PHYS(iova->pfn_lo); ++ ++ info->pages = pages; ++ info->ipu6_iova = *dma_handle; ++ info->size = size; ++ list_add(&info->list, &mmu->vma_list); ++ ++ return info->vaddr; ++ ++out_unmap: ++ while (i--) { ++ ipu6_iova = PFN_PHYS(iova->pfn_lo + i); ++ pci_dma_addr = ipu6_mmu_iova_to_phys(mmu->dmap->mmu_info, ++ ipu6_iova); ++ dma_unmap_page_attrs(&pdev->dev, pci_dma_addr, PAGE_SIZE, ++ DMA_BIDIRECTIONAL, attrs); ++ ++ ipu6_mmu_unmap(mmu->dmap->mmu_info, ipu6_iova, PAGE_SIZE); ++ } ++ ++ __dma_free_buffer(dev, pages, size, attrs); ++ ++out_free_iova: ++ __free_iova(&mmu->dmap->iovad, iova); ++out_kfree: ++ kfree(info); ++ ++ return NULL; ++} ++ ++static void ipu6_dma_free(struct device *dev, size_t size, void *vaddr, ++ dma_addr_t dma_handle, ++ unsigned long attrs) ++{ ++ struct ipu6_mmu *mmu = to_ipu6_bus_device(dev)->mmu; ++ struct pci_dev *pdev = to_ipu6_bus_device(dev)->isp->pdev; ++ struct iova *iova = find_iova(&mmu->dmap->iovad, PHYS_PFN(dma_handle)); ++ dma_addr_t pci_dma_addr, ipu6_iova; ++ struct vm_info *info; ++ struct page **pages; ++ unsigned int i; ++ ++ if (WARN_ON(!iova)) ++ return; ++ ++ info = get_vm_info(mmu, dma_handle); ++ if (WARN_ON(!info)) ++ return; ++ ++ if (WARN_ON(!info->vaddr)) ++ return; ++ ++ if (WARN_ON(!info->pages)) ++ return; ++ ++ list_del(&info->list); ++ ++ size = PAGE_ALIGN(size); ++ ++ pages = info->pages; ++ ++ vunmap(vaddr); ++ ++ for (i = 0; i < PHYS_PFN(size); i++) { ++ ipu6_iova = PFN_PHYS(iova->pfn_lo + i); ++ pci_dma_addr = ipu6_mmu_iova_to_phys(mmu->dmap->mmu_info, ++ ipu6_iova); ++ dma_unmap_page_attrs(&pdev->dev, pci_dma_addr, PAGE_SIZE, ++ DMA_BIDIRECTIONAL, attrs); ++ } ++ ++ ipu6_mmu_unmap(mmu->dmap->mmu_info, PFN_PHYS(iova->pfn_lo), ++ PFN_PHYS(iova_size(iova))); ++ ++ __dma_free_buffer(dev, pages, size, attrs); ++ ++ mmu->tlb_invalidate(mmu); ++ ++ __free_iova(&mmu->dmap->iovad, iova); ++ ++ kfree(info); ++} ++ ++static int ipu6_dma_mmap(struct device *dev, struct vm_area_struct *vma, ++ void *addr, dma_addr_t iova, size_t size, ++ unsigned long attrs) ++{ ++ struct ipu6_mmu *mmu = to_ipu6_bus_device(dev)->mmu; ++ size_t count = PHYS_PFN(PAGE_ALIGN(size)); ++ struct vm_info *info; ++ size_t i; ++ int ret; ++ ++ info = get_vm_info(mmu, iova); ++ if (!info) ++ return -EFAULT; ++ ++ if (!info->vaddr) ++ return -EFAULT; ++ ++ if (vma->vm_start & ~PAGE_MASK) ++ return -EINVAL; ++ ++ if (size > info->size) ++ return -EFAULT; ++ ++ for (i = 0; i < count; i++) { ++ ret = vm_insert_page(vma, vma->vm_start + PFN_PHYS(i), ++ info->pages[i]); ++ if (ret < 0) ++ return ret; ++ } ++ ++ return 0; ++} ++ ++static void ipu6_dma_unmap_sg(struct device *dev, ++ struct scatterlist *sglist, ++ int nents, enum dma_data_direction dir, ++ unsigned long attrs) ++{ ++ struct pci_dev *pdev = to_ipu6_bus_device(dev)->isp->pdev; ++ struct ipu6_mmu *mmu = to_ipu6_bus_device(dev)->mmu; ++ struct iova *iova = find_iova(&mmu->dmap->iovad, ++ PHYS_PFN(sg_dma_address(sglist))); ++ int i, npages, count; ++ struct scatterlist *sg; ++ dma_addr_t pci_dma_addr; ++ ++ if (!nents) ++ return; ++ ++ if (WARN_ON(!iova)) ++ return; ++ ++ if ((attrs & DMA_ATTR_SKIP_CPU_SYNC) == 0) ++ ipu6_dma_sync_sg_for_cpu(dev, sglist, nents, DMA_BIDIRECTIONAL); ++ ++ /* get the nents as orig_nents given by caller */ ++ count = 0; ++ npages = iova_size(iova); ++ for_each_sg(sglist, sg, nents, i) { ++ if (sg_dma_len(sg) == 0 || ++ sg_dma_address(sg) == DMA_MAPPING_ERROR) ++ break; ++ ++ npages -= PHYS_PFN(PAGE_ALIGN(sg_dma_len(sg))); ++ count++; ++ if (npages <= 0) ++ break; ++ } ++ ++ /* ++ * Before IPU6 mmu unmap, return the pci dma address back to sg ++ * assume the nents is less than orig_nents as the least granule ++ * is 1 SZ_4K page ++ */ ++ dev_dbg(dev, "trying to unmap concatenated %u ents\n", count); ++ for_each_sg(sglist, sg, count, i) { ++ dev_dbg(dev, "ipu unmap sg[%d] %pad\n", i, &sg_dma_address(sg)); ++ pci_dma_addr = ipu6_mmu_iova_to_phys(mmu->dmap->mmu_info, ++ sg_dma_address(sg)); ++ dev_dbg(dev, "return pci_dma_addr %pad back to sg[%d]\n", ++ &pci_dma_addr, i); ++ sg_dma_address(sg) = pci_dma_addr; ++ } ++ ++ dev_dbg(dev, "ipu6_mmu_unmap low pfn %lu high pfn %lu\n", ++ iova->pfn_lo, iova->pfn_hi); ++ ipu6_mmu_unmap(mmu->dmap->mmu_info, PFN_PHYS(iova->pfn_lo), ++ PFN_PHYS(iova_size(iova))); ++ ++ mmu->tlb_invalidate(mmu); ++ ++ dma_unmap_sg_attrs(&pdev->dev, sglist, nents, dir, attrs); ++ ++ __free_iova(&mmu->dmap->iovad, iova); ++} ++ ++static int ipu6_dma_map_sg(struct device *dev, struct scatterlist *sglist, ++ int nents, enum dma_data_direction dir, ++ unsigned long attrs) ++{ ++ struct ipu6_mmu *mmu = to_ipu6_bus_device(dev)->mmu; ++ struct pci_dev *pdev = to_ipu6_bus_device(dev)->isp->pdev; ++ struct scatterlist *sg; ++ struct iova *iova; ++ size_t npages = 0; ++ unsigned long iova_addr; ++ int i, count; ++ ++ for_each_sg(sglist, sg, nents, i) { ++ if (sg->offset) { ++ dev_err(dev, "Unsupported non-zero sg[%d].offset %x\n", ++ i, sg->offset); ++ return -EFAULT; ++ } ++ } ++ ++ dev_dbg(dev, "pci_dma_map_sg trying to map %d ents\n", nents); ++ count = dma_map_sg_attrs(&pdev->dev, sglist, nents, dir, attrs); ++ if (count <= 0) { ++ dev_err(dev, "pci_dma_map_sg %d ents failed\n", nents); ++ return 0; ++ } ++ ++ dev_dbg(dev, "pci_dma_map_sg %d ents mapped\n", count); ++ ++ for_each_sg(sglist, sg, count, i) ++ npages += PHYS_PFN(PAGE_ALIGN(sg_dma_len(sg))); ++ ++ iova = alloc_iova(&mmu->dmap->iovad, npages, ++ PHYS_PFN(dma_get_mask(dev)), 0); ++ if (!iova) ++ return 0; ++ ++ dev_dbg(dev, "dmamap: iova low pfn %lu, high pfn %lu\n", iova->pfn_lo, ++ iova->pfn_hi); ++ ++ iova_addr = iova->pfn_lo; ++ for_each_sg(sglist, sg, count, i) { ++ int ret; ++ ++ dev_dbg(dev, "mapping entry %d: iova 0x%llx phy %pad size %d\n", ++ i, PFN_PHYS(iova_addr), &sg_dma_address(sg), ++ sg_dma_len(sg)); ++ ++ ret = ipu6_mmu_map(mmu->dmap->mmu_info, PFN_PHYS(iova_addr), ++ sg_dma_address(sg), ++ PAGE_ALIGN(sg_dma_len(sg))); ++ if (ret) ++ goto out_fail; ++ ++ sg_dma_address(sg) = PFN_PHYS(iova_addr); ++ ++ iova_addr += PHYS_PFN(PAGE_ALIGN(sg_dma_len(sg))); ++ } ++ ++ if ((attrs & DMA_ATTR_SKIP_CPU_SYNC) == 0) ++ ipu6_dma_sync_sg_for_cpu(dev, sglist, nents, DMA_BIDIRECTIONAL); ++ ++ return count; ++ ++out_fail: ++ ipu6_dma_unmap_sg(dev, sglist, i, dir, attrs); ++ ++ return 0; ++} ++ ++/* ++ * Create scatter-list for the already allocated DMA buffer ++ */ ++static int ipu6_dma_get_sgtable(struct device *dev, struct sg_table *sgt, ++ void *cpu_addr, dma_addr_t handle, size_t size, ++ unsigned long attrs) ++{ ++ struct ipu6_mmu *mmu = to_ipu6_bus_device(dev)->mmu; ++ struct vm_info *info; ++ int n_pages; ++ int ret = 0; ++ ++ info = get_vm_info(mmu, handle); ++ if (!info) ++ return -EFAULT; ++ ++ if (!info->vaddr) ++ return -EFAULT; ++ ++ if (WARN_ON(!info->pages)) ++ return -ENOMEM; ++ ++ n_pages = PHYS_PFN(PAGE_ALIGN(size)); ++ ++ ret = sg_alloc_table_from_pages(sgt, info->pages, n_pages, 0, size, ++ GFP_KERNEL); ++ if (ret) ++ dev_warn(dev, "IPU6 get sgt table failed\n"); ++ ++ return ret; ++} ++ ++const struct dma_map_ops ipu6_dma_ops = { ++ .alloc = ipu6_dma_alloc, ++ .free = ipu6_dma_free, ++ .mmap = ipu6_dma_mmap, ++ .map_sg = ipu6_dma_map_sg, ++ .unmap_sg = ipu6_dma_unmap_sg, ++ .sync_single_for_cpu = ipu6_dma_sync_single_for_cpu, ++ .sync_single_for_device = ipu6_dma_sync_single_for_cpu, ++ .sync_sg_for_cpu = ipu6_dma_sync_sg_for_cpu, ++ .sync_sg_for_device = ipu6_dma_sync_sg_for_cpu, ++ .get_sgtable = ipu6_dma_get_sgtable, ++}; +diff --git a/drivers/media/pci/intel/ipu6/ipu6-dma.h b/drivers/media/pci/intel/ipu6/ipu6-dma.h +new file mode 100644 +index 000000000000..c75ad2462368 +--- /dev/null ++++ b/drivers/media/pci/intel/ipu6/ipu6-dma.h +@@ -0,0 +1,19 @@ ++/* SPDX-License-Identifier: GPL-2.0-only */ ++/* Copyright (C) 2013 - 2023 Intel Corporation */ ++ ++#ifndef IPU6_DMA_H ++#define IPU6_DMA_H ++ ++#include <linux/dma-map-ops.h> ++#include <linux/iova.h> ++ ++struct ipu6_mmu_info; ++ ++struct ipu6_dma_mapping { ++ struct ipu6_mmu_info *mmu_info; ++ struct iova_domain iovad; ++}; ++ ++extern const struct dma_map_ops ipu6_dma_ops; ++ ++#endif /* IPU6_DMA_H */ +diff --git a/drivers/media/pci/intel/ipu6/ipu6-mmu.c b/drivers/media/pci/intel/ipu6/ipu6-mmu.c +new file mode 100644 +index 000000000000..dc16d45187a8 +--- /dev/null ++++ b/drivers/media/pci/intel/ipu6/ipu6-mmu.c +@@ -0,0 +1,845 @@ ++// SPDX-License-Identifier: GPL-2.0-only ++/* ++ * Copyright (C) 2013 - 2023 Intel Corporation ++ */ ++#include <asm/barrier.h> ++ ++#include <linux/align.h> ++#include <linux/atomic.h> ++#include <linux/bitops.h> ++#include <linux/bits.h> ++#include <linux/bug.h> ++#include <linux/cacheflush.h> ++#include <linux/dma-mapping.h> ++#include <linux/err.h> ++#include <linux/gfp.h> ++#include <linux/io.h> ++#include <linux/iova.h> ++#include <linux/math.h> ++#include <linux/minmax.h> ++#include <linux/mm.h> ++#include <linux/pfn.h> ++#include <linux/slab.h> ++#include <linux/spinlock.h> ++#include <linux/types.h> ++ ++#include "ipu6.h" ++#include "ipu6-dma.h" ++#include "ipu6-mmu.h" ++#include "ipu6-platform-regs.h" ++ ++#define ISP_PAGE_SHIFT 12 ++#define ISP_PAGE_SIZE BIT(ISP_PAGE_SHIFT) ++#define ISP_PAGE_MASK (~(ISP_PAGE_SIZE - 1)) ++ ++#define ISP_L1PT_SHIFT 22 ++#define ISP_L1PT_MASK (~((1U << ISP_L1PT_SHIFT) - 1)) ++ ++#define ISP_L2PT_SHIFT 12 ++#define ISP_L2PT_MASK (~(ISP_L1PT_MASK | (~(ISP_PAGE_MASK)))) ++ ++#define ISP_L1PT_PTES 1024 ++#define ISP_L2PT_PTES 1024 ++ ++#define ISP_PADDR_SHIFT 12 ++ ++#define REG_TLB_INVALIDATE 0x0000 ++ ++#define REG_L1_PHYS 0x0004 /* 27-bit pfn */ ++#define REG_INFO 0x0008 ++ ++#define TBL_PHYS_ADDR(a) ((phys_addr_t)(a) << ISP_PADDR_SHIFT) ++ ++static void tlb_invalidate(struct ipu6_mmu *mmu) ++{ ++ unsigned long flags; ++ unsigned int i; ++ ++ spin_lock_irqsave(&mmu->ready_lock, flags); ++ if (!mmu->ready) { ++ spin_unlock_irqrestore(&mmu->ready_lock, flags); ++ return; ++ } ++ ++ for (i = 0; i < mmu->nr_mmus; i++) { ++ /* ++ * To avoid the HW bug induced dead lock in some of the IPU6 ++ * MMUs on successive invalidate calls, we need to first do a ++ * read to the page table base before writing the invalidate ++ * register. MMUs which need to implement this WA, will have ++ * the insert_read_before_invalidate flags set as true. ++ * Disregard the return value of the read. ++ */ ++ if (mmu->mmu_hw[i].insert_read_before_invalidate) ++ readl(mmu->mmu_hw[i].base + REG_L1_PHYS); ++ ++ writel(0xffffffff, mmu->mmu_hw[i].base + ++ REG_TLB_INVALIDATE); ++ /* ++ * The TLB invalidation is a "single cycle" (IOMMU clock cycles) ++ * When the actual MMIO write reaches the IPU6 TLB Invalidate ++ * register, wmb() will force the TLB invalidate out if the CPU ++ * attempts to update the IOMMU page table (or sooner). ++ */ ++ wmb(); ++ } ++ spin_unlock_irqrestore(&mmu->ready_lock, flags); ++} ++ ++#ifdef DEBUG ++static void page_table_dump(struct ipu6_mmu_info *mmu_info) ++{ ++ u32 l1_idx; ++ ++ dev_dbg(mmu_info->dev, "begin IOMMU page table dump\n"); ++ ++ for (l1_idx = 0; l1_idx < ISP_L1PT_PTES; l1_idx++) { ++ u32 l2_idx; ++ u32 iova = (phys_addr_t)l1_idx << ISP_L1PT_SHIFT; ++ ++ if (mmu_info->l1_pt[l1_idx] == mmu_info->dummy_l2_pteval) ++ continue; ++ dev_dbg(mmu_info->dev, ++ "l1 entry %u; iovas 0x%8.8x-0x%8.8x, at %pa\n", ++ l1_idx, iova, iova + ISP_PAGE_SIZE, ++ TBL_PHYS_ADDR(mmu_info->l1_pt[l1_idx])); ++ ++ for (l2_idx = 0; l2_idx < ISP_L2PT_PTES; l2_idx++) { ++ u32 *l2_pt = mmu_info->l2_pts[l1_idx]; ++ u32 iova2 = iova + (l2_idx << ISP_L2PT_SHIFT); ++ ++ if (l2_pt[l2_idx] == mmu_info->dummy_page_pteval) ++ continue; ++ ++ dev_dbg(mmu_info->dev, ++ "\tl2 entry %u; iova 0x%8.8x, phys %pa\n", ++ l2_idx, iova2, ++ TBL_PHYS_ADDR(l2_pt[l2_idx])); ++ } ++ } ++ ++ dev_dbg(mmu_info->dev, "end IOMMU page table dump\n"); ++} ++#endif /* DEBUG */ ++ ++static dma_addr_t map_single(struct ipu6_mmu_info *mmu_info, void *ptr) ++{ ++ dma_addr_t dma; ++ ++ dma = dma_map_single(mmu_info->dev, ptr, PAGE_SIZE, DMA_BIDIRECTIONAL); ++ if (dma_mapping_error(mmu_info->dev, dma)) ++ return 0; ++ ++ return dma; ++} ++ ++static int get_dummy_page(struct ipu6_mmu_info *mmu_info) ++{ ++ void *pt = (void *)get_zeroed_page(GFP_ATOMIC | GFP_DMA32); ++ dma_addr_t dma; ++ ++ if (!pt) ++ return -ENOMEM; ++ ++ dev_dbg(mmu_info->dev, "dummy_page: get_zeroed_page() == %p\n", pt); ++ ++ dma = map_single(mmu_info, pt); ++ if (!dma) { ++ dev_err(mmu_info->dev, "Failed to map dummy page\n"); ++ goto err_free_page; ++ } ++ ++ mmu_info->dummy_page = pt; ++ mmu_info->dummy_page_pteval = dma >> ISP_PAGE_SHIFT; ++ ++ return 0; ++ ++err_free_page: ++ free_page((unsigned long)pt); ++ return -ENOMEM; ++} ++ ++static void free_dummy_page(struct ipu6_mmu_info *mmu_info) ++{ ++ dma_unmap_single(mmu_info->dev, ++ TBL_PHYS_ADDR(mmu_info->dummy_page_pteval), ++ PAGE_SIZE, DMA_BIDIRECTIONAL); ++ free_page((unsigned long)mmu_info->dummy_page); ++} ++ ++static int alloc_dummy_l2_pt(struct ipu6_mmu_info *mmu_info) ++{ ++ u32 *pt = (u32 *)get_zeroed_page(GFP_ATOMIC | GFP_DMA32); ++ dma_addr_t dma; ++ unsigned int i; ++ ++ if (!pt) ++ return -ENOMEM; ++ ++ dev_dbg(mmu_info->dev, "dummy_l2: get_zeroed_page() = %p\n", pt); ++ ++ dma = map_single(mmu_info, pt); ++ if (!dma) { ++ dev_err(mmu_info->dev, "Failed to map l2pt page\n"); ++ goto err_free_page; ++ } ++ ++ for (i = 0; i < ISP_L2PT_PTES; i++) ++ pt[i] = mmu_info->dummy_page_pteval; ++ ++ mmu_info->dummy_l2_pt = pt; ++ mmu_info->dummy_l2_pteval = dma >> ISP_PAGE_SHIFT; ++ ++ return 0; ++ ++err_free_page: ++ free_page((unsigned long)pt); ++ return -ENOMEM; ++} ++ ++static void free_dummy_l2_pt(struct ipu6_mmu_info *mmu_info) ++{ ++ dma_unmap_single(mmu_info->dev, ++ TBL_PHYS_ADDR(mmu_info->dummy_l2_pteval), ++ PAGE_SIZE, DMA_BIDIRECTIONAL); ++ free_page((unsigned long)mmu_info->dummy_l2_pt); ++} ++ ++static u32 *alloc_l1_pt(struct ipu6_mmu_info *mmu_info) ++{ ++ u32 *pt = (u32 *)get_zeroed_page(GFP_ATOMIC | GFP_DMA32); ++ dma_addr_t dma; ++ unsigned int i; ++ ++ if (!pt) ++ return NULL; ++ ++ dev_dbg(mmu_info->dev, "alloc_l1: get_zeroed_page() = %p\n", pt); ++ ++ for (i = 0; i < ISP_L1PT_PTES; i++) ++ pt[i] = mmu_info->dummy_l2_pteval; ++ ++ dma = map_single(mmu_info, pt); ++ if (!dma) { ++ dev_err(mmu_info->dev, "Failed to map l1pt page\n"); ++ goto err_free_page; ++ } ++ ++ mmu_info->l1_pt_dma = dma >> ISP_PADDR_SHIFT; ++ dev_dbg(mmu_info->dev, "l1 pt %p mapped at %llx\n", pt, dma); ++ ++ return pt; ++ ++err_free_page: ++ free_page((unsigned long)pt); ++ return NULL; ++} ++ ++static u32 *alloc_l2_pt(struct ipu6_mmu_info *mmu_info) ++{ ++ u32 *pt = (u32 *)get_zeroed_page(GFP_ATOMIC | GFP_DMA32); ++ unsigned int i; ++ ++ if (!pt) ++ return NULL; ++ ++ dev_dbg(mmu_info->dev, "alloc_l2: get_zeroed_page() = %p\n", pt); ++ ++ for (i = 0; i < ISP_L1PT_PTES; i++) ++ pt[i] = mmu_info->dummy_page_pteval; ++ ++ return pt; ++} ++ ++static int l2_map(struct ipu6_mmu_info *mmu_info, unsigned long iova, ++ phys_addr_t paddr, size_t size) ++{ ++ u32 l1_idx = iova >> ISP_L1PT_SHIFT; ++ u32 iova_start = iova; ++ u32 *l2_pt, *l2_virt; ++ unsigned int l2_idx; ++ unsigned long flags; ++ dma_addr_t dma; ++ u32 l1_entry; ++ ++ dev_dbg(mmu_info->dev, ++ "mapping l2 page table for l1 index %u (iova %8.8x)\n", ++ l1_idx, (u32)iova); ++ ++ spin_lock_irqsave(&mmu_info->lock, flags); ++ l1_entry = mmu_info->l1_pt[l1_idx]; ++ if (l1_entry == mmu_info->dummy_l2_pteval) { ++ l2_virt = mmu_info->l2_pts[l1_idx]; ++ if (likely(!l2_virt)) { ++ l2_virt = alloc_l2_pt(mmu_info); ++ if (!l2_virt) { ++ spin_unlock_irqrestore(&mmu_info->lock, flags); ++ return -ENOMEM; ++ } ++ } ++ ++ dma = map_single(mmu_info, l2_virt); ++ if (!dma) { ++ dev_err(mmu_info->dev, "Failed to map l2pt page\n"); ++ free_page((unsigned long)l2_virt); ++ spin_unlock_irqrestore(&mmu_info->lock, flags); ++ return -EINVAL; ++ } ++ ++ l1_entry = dma >> ISP_PADDR_SHIFT; ++ ++ dev_dbg(mmu_info->dev, "page for l1_idx %u %p allocated\n", ++ l1_idx, l2_virt); ++ mmu_info->l1_pt[l1_idx] = l1_entry; ++ mmu_info->l2_pts[l1_idx] = l2_virt; ++ clflush_cache_range((void *)&mmu_info->l1_pt[l1_idx], ++ sizeof(mmu_info->l1_pt[l1_idx])); ++ } ++ ++ l2_pt = mmu_info->l2_pts[l1_idx]; ++ ++ dev_dbg(mmu_info->dev, "l2_pt at %p with dma 0x%x\n", l2_pt, l1_entry); ++ ++ paddr = ALIGN(paddr, ISP_PAGE_SIZE); ++ ++ l2_idx = (iova_start & ISP_L2PT_MASK) >> ISP_L2PT_SHIFT; ++ ++ dev_dbg(mmu_info->dev, "l2_idx %u, phys 0x%8.8x\n", l2_idx, ++ l2_pt[l2_idx]); ++ if (l2_pt[l2_idx] != mmu_info->dummy_page_pteval) { ++ spin_unlock_irqrestore(&mmu_info->lock, flags); ++ return -EINVAL; ++ } ++ ++ l2_pt[l2_idx] = paddr >> ISP_PADDR_SHIFT; ++ ++ clflush_cache_range((void *)&l2_pt[l2_idx], sizeof(l2_pt[l2_idx])); ++ spin_unlock_irqrestore(&mmu_info->lock, flags); ++ ++ dev_dbg(mmu_info->dev, "l2 index %u mapped as 0x%8.8x\n", l2_idx, ++ l2_pt[l2_idx]); ++ ++ return 0; ++} ++ ++static int __ipu6_mmu_map(struct ipu6_mmu_info *mmu_info, unsigned long iova, ++ phys_addr_t paddr, size_t size) ++{ ++ u32 iova_start = round_down(iova, ISP_PAGE_SIZE); ++ u32 iova_end = ALIGN(iova + size, ISP_PAGE_SIZE); ++ ++ dev_dbg(mmu_info->dev, ++ "mapping iova 0x%8.8x--0x%8.8x, size %zu at paddr 0x%10.10llx\n", ++ iova_start, iova_end, size, paddr); ++ ++ return l2_map(mmu_info, iova_start, paddr, size); ++} ++ ++static size_t l2_unmap(struct ipu6_mmu_info *mmu_info, unsigned long iova, ++ phys_addr_t dummy, size_t size) ++{ ++ u32 l1_idx = iova >> ISP_L1PT_SHIFT; ++ u32 iova_start = iova; ++ unsigned int l2_idx; ++ size_t unmapped = 0; ++ unsigned long flags; ++ u32 *l2_pt; ++ ++ dev_dbg(mmu_info->dev, "unmapping l2 page table for l1 index %u (iova 0x%8.8lx)\n", ++ l1_idx, iova); ++ ++ spin_lock_irqsave(&mmu_info->lock, flags); ++ if (mmu_info->l1_pt[l1_idx] == mmu_info->dummy_l2_pteval) { ++ spin_unlock_irqrestore(&mmu_info->lock, flags); ++ dev_err(mmu_info->dev, ++ "unmap iova 0x%8.8lx l1 idx %u which was not mapped\n", ++ iova, l1_idx); ++ return 0; ++ } ++ ++ for (l2_idx = (iova_start & ISP_L2PT_MASK) >> ISP_L2PT_SHIFT; ++ (iova_start & ISP_L1PT_MASK) + (l2_idx << ISP_PAGE_SHIFT) ++ < iova_start + size && l2_idx < ISP_L2PT_PTES; l2_idx++) { ++ l2_pt = mmu_info->l2_pts[l1_idx]; ++ dev_dbg(mmu_info->dev, ++ "unmap l2 index %u with pteval 0x%10.10llx\n", ++ l2_idx, TBL_PHYS_ADDR(l2_pt[l2_idx])); ++ l2_pt[l2_idx] = mmu_info->dummy_page_pteval; ++ ++ clflush_cache_range((void *)&l2_pt[l2_idx], ++ sizeof(l2_pt[l2_idx])); ++ unmapped++; ++ } ++ spin_unlock_irqrestore(&mmu_info->lock, flags); ++ ++ return unmapped << ISP_PAGE_SHIFT; ++} ++ ++static size_t __ipu6_mmu_unmap(struct ipu6_mmu_info *mmu_info, ++ unsigned long iova, size_t size) ++{ ++ return l2_unmap(mmu_info, iova, 0, size); ++} ++ ++static int allocate_trash_buffer(struct ipu6_mmu *mmu) ++{ ++ unsigned int n_pages = PHYS_PFN(PAGE_ALIGN(IPU6_MMUV2_TRASH_RANGE)); ++ struct iova *iova; ++ unsigned int i; ++ dma_addr_t dma; ++ unsigned long iova_addr; ++ int ret; ++ ++ /* Allocate 8MB in iova range */ ++ iova = alloc_iova(&mmu->dmap->iovad, n_pages, ++ PHYS_PFN(mmu->dmap->mmu_info->aperture_end), 0); ++ if (!iova) { ++ dev_err(mmu->dev, "cannot allocate iova range for trash\n"); ++ return -ENOMEM; ++ } ++ ++ dma = dma_map_page(mmu->dmap->mmu_info->dev, mmu->trash_page, 0, ++ PAGE_SIZE, DMA_BIDIRECTIONAL); ++ if (dma_mapping_error(mmu->dmap->mmu_info->dev, dma)) { ++ dev_err(mmu->dmap->mmu_info->dev, "Failed to map trash page\n"); ++ ret = -ENOMEM; ++ goto out_free_iova; ++ } ++ ++ mmu->pci_trash_page = dma; ++ ++ /* ++ * Map the 8MB iova address range to the same physical trash page ++ * mmu->trash_page which is already reserved at the probe ++ */ ++ iova_addr = iova->pfn_lo; ++ for (i = 0; i < n_pages; i++) { ++ ret = ipu6_mmu_map(mmu->dmap->mmu_info, PFN_PHYS(iova_addr), ++ mmu->pci_trash_page, PAGE_SIZE); ++ if (ret) { ++ dev_err(mmu->dev, ++ "mapping trash buffer range failed\n"); ++ goto out_unmap; ++ } ++ ++ iova_addr++; ++ } ++ ++ mmu->iova_trash_page = PFN_PHYS(iova->pfn_lo); ++ dev_dbg(mmu->dev, "iova trash buffer for MMUID: %d is %u\n", ++ mmu->mmid, (unsigned int)mmu->iova_trash_page); ++ return 0; ++ ++out_unmap: ++ ipu6_mmu_unmap(mmu->dmap->mmu_info, PFN_PHYS(iova->pfn_lo), ++ PFN_PHYS(iova_size(iova))); ++ dma_unmap_page(mmu->dmap->mmu_info->dev, mmu->pci_trash_page, ++ PAGE_SIZE, DMA_BIDIRECTIONAL); ++out_free_iova: ++ __free_iova(&mmu->dmap->iovad, iova); ++ return ret; ++} ++ ++int ipu6_mmu_hw_init(struct ipu6_mmu *mmu) ++{ ++ struct ipu6_mmu_info *mmu_info; ++ unsigned long flags; ++ unsigned int i; ++ ++ mmu_info = mmu->dmap->mmu_info; ++ ++ /* Initialise the each MMU HW block */ ++ for (i = 0; i < mmu->nr_mmus; i++) { ++ struct ipu6_mmu_hw *mmu_hw = &mmu->mmu_hw[i]; ++ unsigned int j; ++ u16 block_addr; ++ ++ /* Write page table address per MMU */ ++ writel((phys_addr_t)mmu_info->l1_pt_dma, ++ mmu->mmu_hw[i].base + REG_L1_PHYS); ++ ++ /* Set info bits per MMU */ ++ writel(mmu->mmu_hw[i].info_bits, ++ mmu->mmu_hw[i].base + REG_INFO); ++ ++ /* Configure MMU TLB stream configuration for L1 */ ++ for (j = 0, block_addr = 0; j < mmu_hw->nr_l1streams; ++ block_addr += mmu->mmu_hw[i].l1_block_sz[j], j++) { ++ if (block_addr > IPU6_MAX_LI_BLOCK_ADDR) { ++ dev_err(mmu->dev, "invalid L1 configuration\n"); ++ return -EINVAL; ++ } ++ ++ /* Write block start address for each streams */ ++ writel(block_addr, mmu_hw->base + ++ mmu_hw->l1_stream_id_reg_offset + 4 * j); ++ } ++ ++ /* Configure MMU TLB stream configuration for L2 */ ++ for (j = 0, block_addr = 0; j < mmu_hw->nr_l2streams; ++ block_addr += mmu->mmu_hw[i].l2_block_sz[j], j++) { ++ if (block_addr > IPU6_MAX_L2_BLOCK_ADDR) { ++ dev_err(mmu->dev, "invalid L2 configuration\n"); ++ return -EINVAL; ++ } ++ ++ writel(block_addr, mmu_hw->base + ++ mmu_hw->l2_stream_id_reg_offset + 4 * j); ++ } ++ } ++ ++ if (!mmu->trash_page) { ++ int ret; ++ ++ mmu->trash_page = alloc_page(GFP_KERNEL); ++ if (!mmu->trash_page) { ++ dev_err(mmu->dev, "insufficient memory for trash buffer\n"); ++ return -ENOMEM; ++ } ++ ++ ret = allocate_trash_buffer(mmu); ++ if (ret) { ++ __free_page(mmu->trash_page); ++ mmu->trash_page = NULL; ++ dev_err(mmu->dev, "trash buffer allocation failed\n"); ++ return ret; ++ } ++ } ++ ++ spin_lock_irqsave(&mmu->ready_lock, flags); ++ mmu->ready = true; ++ spin_unlock_irqrestore(&mmu->ready_lock, flags); ++ ++ return 0; ++} ++EXPORT_SYMBOL_NS_GPL(ipu6_mmu_hw_init, INTEL_IPU6); ++ ++static struct ipu6_mmu_info *ipu6_mmu_alloc(struct ipu6_device *isp) ++{ ++ struct ipu6_mmu_info *mmu_info; ++ int ret; ++ ++ mmu_info = kzalloc(sizeof(*mmu_info), GFP_KERNEL); ++ if (!mmu_info) ++ return NULL; ++ ++ mmu_info->aperture_start = 0; ++ mmu_info->aperture_end = DMA_BIT_MASK(isp->secure_mode ? ++ IPU6_MMU_ADDR_BITS : ++ IPU6_MMU_ADDR_BITS_NON_SECURE); ++ mmu_info->pgsize_bitmap = SZ_4K; ++ mmu_info->dev = &isp->pdev->dev; ++ ++ ret = get_dummy_page(mmu_info); ++ if (ret) ++ goto err_free_info; ++ ++ ret = alloc_dummy_l2_pt(mmu_info); ++ if (ret) ++ goto err_free_dummy_page; ++ ++ mmu_info->l2_pts = vzalloc(ISP_L2PT_PTES * sizeof(*mmu_info->l2_pts)); ++ if (!mmu_info->l2_pts) ++ goto err_free_dummy_l2_pt; ++ ++ /* ++ * We always map the L1 page table (a single page as well as ++ * the L2 page tables). ++ */ ++ mmu_info->l1_pt = alloc_l1_pt(mmu_info); ++ if (!mmu_info->l1_pt) ++ goto err_free_l2_pts; ++ ++ spin_lock_init(&mmu_info->lock); ++ ++ dev_dbg(mmu_info->dev, "domain initialised\n"); ++ ++ return mmu_info; ++ ++err_free_l2_pts: ++ vfree(mmu_info->l2_pts); ++err_free_dummy_l2_pt: ++ free_dummy_l2_pt(mmu_info); ++err_free_dummy_page: ++ free_dummy_page(mmu_info); ++err_free_info: ++ kfree(mmu_info); ++ ++ return NULL; ++} ++ ++void ipu6_mmu_hw_cleanup(struct ipu6_mmu *mmu) ++{ ++ unsigned long flags; ++ ++ spin_lock_irqsave(&mmu->ready_lock, flags); ++ mmu->ready = false; ++ spin_unlock_irqrestore(&mmu->ready_lock, flags); ++} ++EXPORT_SYMBOL_NS_GPL(ipu6_mmu_hw_cleanup, INTEL_IPU6); ++ ++static struct ipu6_dma_mapping *alloc_dma_mapping(struct ipu6_device *isp) ++{ ++ struct ipu6_dma_mapping *dmap; ++ ++ dmap = kzalloc(sizeof(*dmap), GFP_KERNEL); ++ if (!dmap) ++ return NULL; ++ ++ dmap->mmu_info = ipu6_mmu_alloc(isp); ++ if (!dmap->mmu_info) { ++ kfree(dmap); ++ return NULL; ++ } ++ ++ init_iova_domain(&dmap->iovad, SZ_4K, 1); ++ dmap->mmu_info->dmap = dmap; ++ ++ dev_dbg(&isp->pdev->dev, "alloc mapping\n"); ++ ++ iova_cache_get(); ++ ++ return dmap; ++} ++ ++phys_addr_t ipu6_mmu_iova_to_phys(struct ipu6_mmu_info *mmu_info, ++ dma_addr_t iova) ++{ ++ phys_addr_t phy_addr; ++ unsigned long flags; ++ u32 *l2_pt; ++ ++ spin_lock_irqsave(&mmu_info->lock, flags); ++ l2_pt = mmu_info->l2_pts[iova >> ISP_L1PT_SHIFT]; ++ phy_addr = (phys_addr_t)l2_pt[(iova & ISP_L2PT_MASK) >> ISP_L2PT_SHIFT]; ++ phy_addr <<= ISP_PAGE_SHIFT; ++ spin_unlock_irqrestore(&mmu_info->lock, flags); ++ ++ return phy_addr; ++} ++ ++static size_t ipu6_mmu_pgsize(unsigned long pgsize_bitmap, ++ unsigned long addr_merge, size_t size) ++{ ++ unsigned int pgsize_idx; ++ size_t pgsize; ++ ++ /* Max page size that still fits into 'size' */ ++ pgsize_idx = __fls(size); ++ ++ if (likely(addr_merge)) { ++ /* Max page size allowed by address */ ++ unsigned int align_pgsize_idx = __ffs(addr_merge); ++ ++ pgsize_idx = min(pgsize_idx, align_pgsize_idx); ++ } ++ ++ pgsize = (1UL << (pgsize_idx + 1)) - 1; ++ pgsize &= pgsize_bitmap; ++ ++ WARN_ON(!pgsize); ++ ++ /* pick the biggest page */ ++ pgsize_idx = __fls(pgsize); ++ pgsize = 1UL << pgsize_idx; ++ ++ return pgsize; ++} ++ ++size_t ipu6_mmu_unmap(struct ipu6_mmu_info *mmu_info, unsigned long iova, ++ size_t size) ++{ ++ size_t unmapped_page, unmapped = 0; ++ unsigned int min_pagesz; ++ ++ /* find out the minimum page size supported */ ++ min_pagesz = 1 << __ffs(mmu_info->pgsize_bitmap); ++ ++ /* ++ * The virtual address and the size of the mapping must be ++ * aligned (at least) to the size of the smallest page supported ++ * by the hardware ++ */ ++ if (!IS_ALIGNED(iova | size, min_pagesz)) { ++ dev_err(NULL, "unaligned: iova 0x%lx size 0x%zx min_pagesz 0x%x\n", ++ iova, size, min_pagesz); ++ return -EINVAL; ++ } ++ ++ /* ++ * Keep iterating until we either unmap 'size' bytes (or more) ++ * or we hit an area that isn't mapped. ++ */ ++ while (unmapped < size) { ++ size_t pgsize = ipu6_mmu_pgsize(mmu_info->pgsize_bitmap, ++ iova, size - unmapped); ++ ++ unmapped_page = __ipu6_mmu_unmap(mmu_info, iova, pgsize); ++ if (!unmapped_page) ++ break; ++ ++ dev_dbg(mmu_info->dev, "unmapped: iova 0x%lx size 0x%zx\n", ++ iova, unmapped_page); ++ ++ iova += unmapped_page; ++ unmapped += unmapped_page; ++ } ++ ++ return unmapped; ++} ++ ++int ipu6_mmu_map(struct ipu6_mmu_info *mmu_info, unsigned long iova, ++ phys_addr_t paddr, size_t size) ++{ ++ unsigned long orig_iova = iova; ++ unsigned int min_pagesz; ++ size_t orig_size = size; ++ int ret = 0; ++ ++ if (mmu_info->pgsize_bitmap == 0UL) ++ return -ENODEV; ++ ++ /* find out the minimum page size supported */ ++ min_pagesz = 1 << __ffs(mmu_info->pgsize_bitmap); ++ ++ /* ++ * both the virtual address and the physical one, as well as ++ * the size of the mapping, must be aligned (at least) to the ++ * size of the smallest page supported by the hardware ++ */ ++ if (!IS_ALIGNED(iova | paddr | size, min_pagesz)) { ++ dev_err(mmu_info->dev, ++ "unaligned: iova %lx pa %pa size %zx min_pagesz %x\n", ++ iova, &paddr, size, min_pagesz); ++ return -EINVAL; ++ } ++ ++ dev_dbg(mmu_info->dev, "map: iova 0x%lx pa %pa size 0x%zx\n", ++ iova, &paddr, size); ++ ++ while (size) { ++ size_t pgsize = ipu6_mmu_pgsize(mmu_info->pgsize_bitmap, ++ iova | paddr, size); ++ ++ dev_dbg(mmu_info->dev, ++ "mapping: iova 0x%lx pa %pa pgsize 0x%zx\n", ++ iova, &paddr, pgsize); ++ ++ ret = __ipu6_mmu_map(mmu_info, iova, paddr, pgsize); ++ if (ret) ++ break; ++ ++ iova += pgsize; ++ paddr += pgsize; ++ size -= pgsize; ++ } ++ ++ /* unroll mapping in case something went wrong */ ++ if (ret) ++ ipu6_mmu_unmap(mmu_info, orig_iova, orig_size - size); ++ ++ return ret; ++} ++ ++static void ipu6_mmu_destroy(struct ipu6_mmu *mmu) ++{ ++ struct ipu6_dma_mapping *dmap = mmu->dmap; ++ struct ipu6_mmu_info *mmu_info = dmap->mmu_info; ++ struct iova *iova; ++ u32 l1_idx; ++ ++ if (mmu->iova_trash_page) { ++ iova = find_iova(&dmap->iovad, PHYS_PFN(mmu->iova_trash_page)); ++ if (iova) { ++ /* unmap and free the trash buffer iova */ ++ ipu6_mmu_unmap(mmu_info, PFN_PHYS(iova->pfn_lo), ++ PFN_PHYS(iova_size(iova))); ++ __free_iova(&dmap->iovad, iova); ++ } else { ++ dev_err(mmu->dev, "trash buffer iova not found.\n"); ++ } ++ ++ mmu->iova_trash_page = 0; ++ dma_unmap_page(mmu_info->dev, mmu->pci_trash_page, ++ PAGE_SIZE, DMA_BIDIRECTIONAL); ++ mmu->pci_trash_page = 0; ++ __free_page(mmu->trash_page); ++ } ++ ++ for (l1_idx = 0; l1_idx < ISP_L1PT_PTES; l1_idx++) { ++ if (mmu_info->l1_pt[l1_idx] != mmu_info->dummy_l2_pteval) { ++ dma_unmap_single(mmu_info->dev, ++ TBL_PHYS_ADDR(mmu_info->l1_pt[l1_idx]), ++ PAGE_SIZE, DMA_BIDIRECTIONAL); ++ free_page((unsigned long)mmu_info->l2_pts[l1_idx]); ++ } ++ } ++ ++ vfree(mmu_info->l2_pts); ++ free_dummy_page(mmu_info); ++ dma_unmap_single(mmu_info->dev, TBL_PHYS_ADDR(mmu_info->l1_pt_dma), ++ PAGE_SIZE, DMA_BIDIRECTIONAL); ++ free_page((unsigned long)mmu_info->dummy_l2_pt); ++ free_page((unsigned long)mmu_info->l1_pt); ++ kfree(mmu_info); ++} ++ ++struct ipu6_mmu *ipu6_mmu_init(struct device *dev, ++ void __iomem *base, int mmid, ++ const struct ipu6_hw_variants *hw) ++{ ++ struct ipu6_device *isp = pci_get_drvdata(to_pci_dev(dev)); ++ struct ipu6_mmu_pdata *pdata; ++ struct ipu6_mmu *mmu; ++ unsigned int i; ++ ++ if (hw->nr_mmus > IPU6_MMU_MAX_DEVICES) ++ return ERR_PTR(-EINVAL); ++ ++ pdata = devm_kzalloc(dev, sizeof(*pdata), GFP_KERNEL); ++ if (!pdata) ++ return ERR_PTR(-ENOMEM); ++ ++ for (i = 0; i < hw->nr_mmus; i++) { ++ struct ipu6_mmu_hw *pdata_mmu = &pdata->mmu_hw[i]; ++ const struct ipu6_mmu_hw *src_mmu = &hw->mmu_hw[i]; ++ ++ if (src_mmu->nr_l1streams > IPU6_MMU_MAX_TLB_L1_STREAMS || ++ src_mmu->nr_l2streams > IPU6_MMU_MAX_TLB_L2_STREAMS) ++ return ERR_PTR(-EINVAL); ++ ++ *pdata_mmu = *src_mmu; ++ pdata_mmu->base = base + src_mmu->offset; ++ } ++ ++ mmu = devm_kzalloc(dev, sizeof(*mmu), GFP_KERNEL); ++ if (!mmu) ++ return ERR_PTR(-ENOMEM); ++ ++ mmu->mmid = mmid; ++ mmu->mmu_hw = pdata->mmu_hw; ++ mmu->nr_mmus = hw->nr_mmus; ++ mmu->tlb_invalidate = tlb_invalidate; ++ mmu->ready = false; ++ INIT_LIST_HEAD(&mmu->vma_list); ++ spin_lock_init(&mmu->ready_lock); ++ ++ mmu->dmap = alloc_dma_mapping(isp); ++ if (!mmu->dmap) { ++ dev_err(dev, "can't alloc dma mapping\n"); ++ return ERR_PTR(-ENOMEM); ++ } ++ ++ return mmu; ++} ++ ++void ipu6_mmu_cleanup(struct ipu6_mmu *mmu) ++{ ++ struct ipu6_dma_mapping *dmap = mmu->dmap; ++ ++ ipu6_mmu_destroy(mmu); ++ mmu->dmap = NULL; ++ iova_cache_put(); ++ put_iova_domain(&dmap->iovad); ++ kfree(dmap); ++} +diff --git a/drivers/media/pci/intel/ipu6/ipu6-mmu.h b/drivers/media/pci/intel/ipu6/ipu6-mmu.h +new file mode 100644 +index 000000000000..95df7931a2e5 +--- /dev/null ++++ b/drivers/media/pci/intel/ipu6/ipu6-mmu.h +@@ -0,0 +1,73 @@ ++/* SPDX-License-Identifier: GPL-2.0-only */ ++/* Copyright (C) 2013 - 2023 Intel Corporation */ ++ ++#ifndef IPU6_MMU_H ++#define IPU6_MMU_H ++ ++#define ISYS_MMID 1 ++#define PSYS_MMID 0 ++ ++#include <linux/list.h> ++#include <linux/spinlock_types.h> ++#include <linux/types.h> ++ ++struct device; ++struct page; ++struct ipu6_hw_variants; ++ ++struct ipu6_mmu_info { ++ struct device *dev; ++ ++ u32 *l1_pt; ++ u32 l1_pt_dma; ++ u32 **l2_pts; ++ ++ u32 *dummy_l2_pt; ++ u32 dummy_l2_pteval; ++ void *dummy_page; ++ u32 dummy_page_pteval; ++ ++ dma_addr_t aperture_start; ++ dma_addr_t aperture_end; ++ unsigned long pgsize_bitmap; ++ ++ spinlock_t lock; /* Serialize access to users */ ++ struct ipu6_dma_mapping *dmap; ++}; ++ ++struct ipu6_mmu { ++ struct list_head node; ++ ++ struct ipu6_mmu_hw *mmu_hw; ++ unsigned int nr_mmus; ++ unsigned int mmid; ++ ++ phys_addr_t pgtbl; ++ struct device *dev; ++ ++ struct ipu6_dma_mapping *dmap; ++ struct list_head vma_list; ++ ++ struct page *trash_page; ++ dma_addr_t pci_trash_page; /* IOVA from PCI DMA services (parent) */ ++ dma_addr_t iova_trash_page; /* IOVA for IPU6 child nodes to use */ ++ ++ bool ready; ++ spinlock_t ready_lock; /* Serialize access to bool ready */ ++ ++ void (*tlb_invalidate)(struct ipu6_mmu *mmu); ++}; ++ ++struct ipu6_mmu *ipu6_mmu_init(struct device *dev, ++ void __iomem *base, int mmid, ++ const struct ipu6_hw_variants *hw); ++void ipu6_mmu_cleanup(struct ipu6_mmu *mmu); ++int ipu6_mmu_hw_init(struct ipu6_mmu *mmu); ++void ipu6_mmu_hw_cleanup(struct ipu6_mmu *mmu); ++int ipu6_mmu_map(struct ipu6_mmu_info *mmu_info, unsigned long iova, ++ phys_addr_t paddr, size_t size); ++size_t ipu6_mmu_unmap(struct ipu6_mmu_info *mmu_info, unsigned long iova, ++ size_t size); ++phys_addr_t ipu6_mmu_iova_to_phys(struct ipu6_mmu_info *mmu_info, ++ dma_addr_t iova); ++#endif +-- +2.43.0 + +From 6e686c2ff23611004d6e4f9a543116d65cbcf5f3 Mon Sep 17 00:00:00 2001 +From: Bingbu Cao <bingbu.cao@intel.com> +Date: Thu, 11 Jan 2024 14:55:20 +0800 +Subject: [PATCH 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 <bingbu.cao@intel.com> +Link: https://lore.kernel.org/r/20240111065531.2418836-7-bingbu.cao@intel.com +Signed-off-by: Hans de Goede <hdegoede@redhat.com> +--- + drivers/media/pci/intel/ipu6/ipu6-fw-com.c | 413 +++++++++++++++++++++ + drivers/media/pci/intel/ipu6/ipu6-fw-com.h | 47 +++ + 2 files changed, 460 insertions(+) + create mode 100644 drivers/media/pci/intel/ipu6/ipu6-fw-com.c + create mode 100644 drivers/media/pci/intel/ipu6/ipu6-fw-com.h + +diff --git a/drivers/media/pci/intel/ipu6/ipu6-fw-com.c b/drivers/media/pci/intel/ipu6/ipu6-fw-com.c +new file mode 100644 +index 000000000000..0f893f44e04c +--- /dev/null ++++ b/drivers/media/pci/intel/ipu6/ipu6-fw-com.c +@@ -0,0 +1,413 @@ ++// SPDX-License-Identifier: GPL-2.0-only ++/* ++ * Copyright (C) 2013 - 2023 Intel Corporation ++ */ ++ ++#include <linux/device.h> ++#include <linux/dma-mapping.h> ++#include <linux/io.h> ++#include <linux/math.h> ++#include <linux/overflow.h> ++#include <linux/slab.h> ++#include <linux/types.h> ++ ++#include "ipu6-bus.h" ++#include "ipu6-fw-com.h" ++ ++/* ++ * FWCOM layer is a shared resource between FW and driver. It consist ++ * of token queues to both send and receive directions. Queue is simply ++ * an array of structures with read and write indexes to the queue. ++ * There are 1...n queues to both directions. Queues locates in ++ * system RAM and are mapped to ISP MMU so that both CPU and ISP can ++ * see the same buffer. Indexes are located in ISP DMEM so that FW code ++ * can poll those with very low latency and cost. CPU access to indexes is ++ * more costly but that happens only at message sending time and ++ * interrupt triggered message handling. CPU doesn't need to poll indexes. ++ * wr_reg / rd_reg are offsets to those dmem location. They are not ++ * the indexes itself. ++ */ ++ ++/* Shared structure between driver and FW - do not modify */ ++struct ipu6_fw_sys_queue { ++ u64 host_address; ++ u32 vied_address; ++ u32 size; ++ u32 token_size; ++ u32 wr_reg; /* reg number in subsystem's regmem */ ++ u32 rd_reg; ++ u32 _align; ++} __packed; ++ ++struct ipu6_fw_sys_queue_res { ++ u64 host_address; ++ u32 vied_address; ++ u32 reg; ++} __packed; ++ ++enum syscom_state { ++ /* Program load or explicit host setting should init to this */ ++ SYSCOM_STATE_UNINIT = 0x57a7e000, ++ /* SP Syscom sets this when it is ready for use */ ++ SYSCOM_STATE_READY = 0x57a7e001, ++ /* SP Syscom sets this when no more syscom accesses will happen */ ++ SYSCOM_STATE_INACTIVE = 0x57a7e002, ++}; ++ ++enum syscom_cmd { ++ /* Program load or explicit host setting should init to this */ ++ SYSCOM_COMMAND_UNINIT = 0x57a7f000, ++ /* Host Syscom requests syscom to become inactive */ ++ SYSCOM_COMMAND_INACTIVE = 0x57a7f001, ++}; ++ ++/* firmware config: data that sent from the host to SP via DDR */ ++/* Cell copies data into a context */ ++ ++struct ipu6_fw_syscom_config { ++ u32 firmware_address; ++ ++ u32 num_input_queues; ++ u32 num_output_queues; ++ ++ /* ISP pointers to an array of ipu6_fw_sys_queue structures */ ++ u32 input_queue; ++ u32 output_queue; ++ ++ /* ISYS / PSYS private data */ ++ u32 specific_addr; ++ u32 specific_size; ++}; ++ ++struct ipu6_fw_com_context { ++ struct ipu6_bus_device *adev; ++ void __iomem *dmem_addr; ++ int (*cell_ready)(struct ipu6_bus_device *adev); ++ void (*cell_start)(struct ipu6_bus_device *adev); ++ ++ void *dma_buffer; ++ dma_addr_t dma_addr; ++ unsigned int dma_size; ++ unsigned long attrs; ++ ++ struct ipu6_fw_sys_queue *input_queue; /* array of host to SP queues */ ++ struct ipu6_fw_sys_queue *output_queue; /* array of SP to host */ ++ ++ u32 config_vied_addr; ++ ++ unsigned int buttress_boot_offset; ++ void __iomem *base_addr; ++}; ++ ++#define FW_COM_WR_REG 0 ++#define FW_COM_RD_REG 4 ++ ++#define REGMEM_OFFSET 0 ++#define TUNIT_MAGIC_PATTERN 0x5a5a5a5a ++ ++enum regmem_id { ++ /* pass pkg_dir address to SPC in non-secure mode */ ++ PKG_DIR_ADDR_REG = 0, ++ /* Tunit CFG blob for secure - provided by host.*/ ++ TUNIT_CFG_DWR_REG = 1, ++ /* syscom commands - modified by the host */ ++ SYSCOM_COMMAND_REG = 2, ++ /* Store interrupt status - updated by SP */ ++ SYSCOM_IRQ_REG = 3, ++ /* first syscom queue pointer register */ ++ SYSCOM_QPR_BASE_REG = 4 ++}; ++ ++#define BUTTRESS_FW_BOOT_PARAMS_0 0x4000 ++#define BUTTRESS_FW_BOOT_PARAM_REG(base, offset, id) \ ++ ((base) + BUTTRESS_FW_BOOT_PARAMS_0 + ((offset) + (id)) * 4) ++ ++enum buttress_syscom_id { ++ /* pass syscom configuration to SPC */ ++ SYSCOM_CONFIG_ID = 0, ++ /* syscom state - modified by SP */ ++ SYSCOM_STATE_ID = 1, ++ /* syscom vtl0 addr mask */ ++ SYSCOM_VTL0_ADDR_MASK_ID = 2, ++ SYSCOM_ID_MAX ++}; ++ ++static void ipu6_sys_queue_init(struct ipu6_fw_sys_queue *q, unsigned int size, ++ unsigned int token_size, ++ struct ipu6_fw_sys_queue_res *res) ++{ ++ unsigned int buf_size = (size + 1) * token_size; ++ ++ q->size = size + 1; ++ q->token_size = token_size; ++ ++ /* acquire the shared buffer space */ ++ q->host_address = res->host_address; ++ res->host_address += buf_size; ++ q->vied_address = res->vied_address; ++ res->vied_address += buf_size; ++ ++ /* acquire the shared read and writer pointers */ ++ q->wr_reg = res->reg; ++ res->reg++; ++ q->rd_reg = res->reg; ++ res->reg++; ++} ++ ++void *ipu6_fw_com_prepare(struct ipu6_fw_com_cfg *cfg, ++ struct ipu6_bus_device *adev, void __iomem *base) ++{ ++ size_t conf_size, inq_size, outq_size, specific_size; ++ struct ipu6_fw_syscom_config *config_host_addr; ++ unsigned int sizeinput = 0, sizeoutput = 0; ++ struct ipu6_fw_sys_queue_res res; ++ struct ipu6_fw_com_context *ctx; ++ struct device *dev = &adev->auxdev.dev; ++ size_t sizeall, offset; ++ unsigned long attrs = 0; ++ void *specific_host_addr; ++ unsigned int i; ++ ++ if (!cfg || !cfg->cell_start || !cfg->cell_ready) ++ return NULL; ++ ++ ctx = kzalloc(sizeof(*ctx), GFP_KERNEL); ++ if (!ctx) ++ return NULL; ++ ctx->dmem_addr = base + cfg->dmem_addr + REGMEM_OFFSET; ++ ctx->adev = adev; ++ ctx->cell_start = cfg->cell_start; ++ ctx->cell_ready = cfg->cell_ready; ++ ctx->buttress_boot_offset = cfg->buttress_boot_offset; ++ ctx->base_addr = base; ++ ++ /* ++ * Allocate DMA mapped memory. Allocate one big chunk. ++ */ ++ /* Base cfg for FW */ ++ conf_size = roundup(sizeof(struct ipu6_fw_syscom_config), 8); ++ /* Descriptions of the queues */ ++ inq_size = size_mul(cfg->num_input_queues, ++ sizeof(struct ipu6_fw_sys_queue)); ++ outq_size = size_mul(cfg->num_output_queues, ++ sizeof(struct ipu6_fw_sys_queue)); ++ /* FW specific information structure */ ++ specific_size = roundup(cfg->specific_size, 8); ++ ++ sizeall = conf_size + inq_size + outq_size + specific_size; ++ ++ for (i = 0; i < cfg->num_input_queues; i++) ++ sizeinput += size_mul(cfg->input[i].queue_size + 1, ++ cfg->input[i].token_size); ++ ++ for (i = 0; i < cfg->num_output_queues; i++) ++ sizeoutput += size_mul(cfg->output[i].queue_size + 1, ++ cfg->output[i].token_size); ++ ++ sizeall += sizeinput + sizeoutput; ++ ++ ctx->dma_buffer = dma_alloc_attrs(dev, sizeall, &ctx->dma_addr, ++ GFP_KERNEL, attrs); ++ ctx->attrs = attrs; ++ if (!ctx->dma_buffer) { ++ dev_err(dev, "failed to allocate dma memory\n"); ++ kfree(ctx); ++ return NULL; ++ } ++ ++ ctx->dma_size = sizeall; ++ ++ config_host_addr = ctx->dma_buffer; ++ ctx->config_vied_addr = ctx->dma_addr; ++ ++ offset = conf_size; ++ ctx->input_queue = ctx->dma_buffer + offset; ++ config_host_addr->input_queue = ctx->dma_addr + offset; ++ config_host_addr->num_input_queues = cfg->num_input_queues; ++ ++ offset += inq_size; ++ ctx->output_queue = ctx->dma_buffer + offset; ++ config_host_addr->output_queue = ctx->dma_addr + offset; ++ config_host_addr->num_output_queues = cfg->num_output_queues; ++ ++ /* copy firmware specific data */ ++ offset += outq_size; ++ specific_host_addr = ctx->dma_buffer + offset; ++ config_host_addr->specific_addr = ctx->dma_addr + offset; ++ config_host_addr->specific_size = cfg->specific_size; ++ if (cfg->specific_addr && cfg->specific_size) ++ memcpy(specific_host_addr, cfg->specific_addr, ++ cfg->specific_size); ++ ++ /* initialize input queues */ ++ offset += specific_size; ++ res.reg = SYSCOM_QPR_BASE_REG; ++ res.host_address = (u64)(ctx->dma_buffer + offset); ++ res.vied_address = ctx->dma_addr + offset; ++ for (i = 0; i < cfg->num_input_queues; i++) ++ ipu6_sys_queue_init(ctx->input_queue + i, ++ cfg->input[i].queue_size, ++ cfg->input[i].token_size, &res); ++ ++ /* initialize output queues */ ++ offset += sizeinput; ++ res.host_address = (u64)(ctx->dma_buffer + offset); ++ res.vied_address = ctx->dma_addr + offset; ++ for (i = 0; i < cfg->num_output_queues; i++) { ++ ipu6_sys_queue_init(ctx->output_queue + i, ++ cfg->output[i].queue_size, ++ cfg->output[i].token_size, &res); ++ } ++ ++ return ctx; ++} ++EXPORT_SYMBOL_NS_GPL(ipu6_fw_com_prepare, INTEL_IPU6); ++ ++int ipu6_fw_com_open(struct ipu6_fw_com_context *ctx) ++{ ++ /* write magic pattern to disable the tunit trace */ ++ writel(TUNIT_MAGIC_PATTERN, ctx->dmem_addr + TUNIT_CFG_DWR_REG * 4); ++ /* Check if SP is in valid state */ ++ if (!ctx->cell_ready(ctx->adev)) ++ return -EIO; ++ ++ /* store syscom uninitialized command */ ++ writel(SYSCOM_COMMAND_UNINIT, ctx->dmem_addr + SYSCOM_COMMAND_REG * 4); ++ ++ /* store syscom uninitialized state */ ++ writel(SYSCOM_STATE_UNINIT, ++ BUTTRESS_FW_BOOT_PARAM_REG(ctx->base_addr, ++ ctx->buttress_boot_offset, ++ SYSCOM_STATE_ID)); ++ ++ /* store firmware configuration address */ ++ writel(ctx->config_vied_addr, ++ BUTTRESS_FW_BOOT_PARAM_REG(ctx->base_addr, ++ ctx->buttress_boot_offset, ++ SYSCOM_CONFIG_ID)); ++ ctx->cell_start(ctx->adev); ++ ++ return 0; ++} ++EXPORT_SYMBOL_NS_GPL(ipu6_fw_com_open, INTEL_IPU6); ++ ++int ipu6_fw_com_close(struct ipu6_fw_com_context *ctx) ++{ ++ int state; ++ ++ state = readl(BUTTRESS_FW_BOOT_PARAM_REG(ctx->base_addr, ++ ctx->buttress_boot_offset, ++ SYSCOM_STATE_ID)); ++ if (state != SYSCOM_STATE_READY) ++ return -EBUSY; ++ ++ /* set close request flag */ ++ writel(SYSCOM_COMMAND_INACTIVE, ctx->dmem_addr + ++ SYSCOM_COMMAND_REG * 4); ++ ++ return 0; ++} ++EXPORT_SYMBOL_NS_GPL(ipu6_fw_com_close, INTEL_IPU6); ++ ++int ipu6_fw_com_release(struct ipu6_fw_com_context *ctx, unsigned int force) ++{ ++ /* check if release is forced, an verify cell state if it is not */ ++ if (!force && !ctx->cell_ready(ctx->adev)) ++ return -EBUSY; ++ ++ dma_free_attrs(&ctx->adev->auxdev.dev, ctx->dma_size, ++ ctx->dma_buffer, ctx->dma_addr, ctx->attrs); ++ kfree(ctx); ++ return 0; ++} ++EXPORT_SYMBOL_NS_GPL(ipu6_fw_com_release, INTEL_IPU6); ++ ++bool ipu6_fw_com_ready(struct ipu6_fw_com_context *ctx) ++{ ++ int state; ++ ++ state = readl(BUTTRESS_FW_BOOT_PARAM_REG(ctx->base_addr, ++ ctx->buttress_boot_offset, ++ SYSCOM_STATE_ID)); ++ ++ return state == SYSCOM_STATE_READY; ++} ++EXPORT_SYMBOL_NS_GPL(ipu6_fw_com_ready, INTEL_IPU6); ++ ++void *ipu6_send_get_token(struct ipu6_fw_com_context *ctx, int q_nbr) ++{ ++ struct ipu6_fw_sys_queue *q = &ctx->input_queue[q_nbr]; ++ void __iomem *q_dmem = ctx->dmem_addr + q->wr_reg * 4; ++ unsigned int wr, rd; ++ unsigned int packets; ++ unsigned int index; ++ ++ wr = readl(q_dmem + FW_COM_WR_REG); ++ rd = readl(q_dmem + FW_COM_RD_REG); ++ ++ if (WARN_ON_ONCE(wr >= q->size || rd >= q->size)) ++ return NULL; ++ ++ if (wr < rd) ++ packets = rd - wr - 1; ++ else ++ packets = q->size - (wr - rd + 1); ++ ++ if (!packets) ++ return NULL; ++ ++ index = readl(q_dmem + FW_COM_WR_REG); ++ ++ return (void *)(q->host_address + index * q->token_size); ++} ++EXPORT_SYMBOL_NS_GPL(ipu6_send_get_token, INTEL_IPU6); ++ ++void ipu6_send_put_token(struct ipu6_fw_com_context *ctx, int q_nbr) ++{ ++ struct ipu6_fw_sys_queue *q = &ctx->input_queue[q_nbr]; ++ void __iomem *q_dmem = ctx->dmem_addr + q->wr_reg * 4; ++ unsigned int wr = readl(q_dmem + FW_COM_WR_REG) + 1; ++ ++ if (wr >= q->size) ++ wr = 0; ++ ++ writel(wr, q_dmem + FW_COM_WR_REG); ++} ++EXPORT_SYMBOL_NS_GPL(ipu6_send_put_token, INTEL_IPU6); ++ ++void *ipu6_recv_get_token(struct ipu6_fw_com_context *ctx, int q_nbr) ++{ ++ struct ipu6_fw_sys_queue *q = &ctx->output_queue[q_nbr]; ++ void __iomem *q_dmem = ctx->dmem_addr + q->wr_reg * 4; ++ unsigned int wr, rd; ++ unsigned int packets; ++ ++ wr = readl(q_dmem + FW_COM_WR_REG); ++ rd = readl(q_dmem + FW_COM_RD_REG); ++ ++ if (WARN_ON_ONCE(wr >= q->size || rd >= q->size)) ++ return NULL; ++ ++ if (wr < rd) ++ wr += q->size; ++ ++ packets = wr - rd; ++ if (!packets) ++ return NULL; ++ ++ return (void *)(q->host_address + rd * q->token_size); ++} ++EXPORT_SYMBOL_NS_GPL(ipu6_recv_get_token, INTEL_IPU6); ++ ++void ipu6_recv_put_token(struct ipu6_fw_com_context *ctx, int q_nbr) ++{ ++ struct ipu6_fw_sys_queue *q = &ctx->output_queue[q_nbr]; ++ void __iomem *q_dmem = ctx->dmem_addr + q->wr_reg * 4; ++ unsigned int rd = readl(q_dmem + FW_COM_RD_REG) + 1; ++ ++ if (rd >= q->size) ++ rd = 0; ++ ++ writel(rd, q_dmem + FW_COM_RD_REG); ++} ++EXPORT_SYMBOL_NS_GPL(ipu6_recv_put_token, INTEL_IPU6); +diff --git a/drivers/media/pci/intel/ipu6/ipu6-fw-com.h b/drivers/media/pci/intel/ipu6/ipu6-fw-com.h +new file mode 100644 +index 000000000000..660c406b3ac9 +--- /dev/null ++++ b/drivers/media/pci/intel/ipu6/ipu6-fw-com.h +@@ -0,0 +1,47 @@ ++/* SPDX-License-Identifier: GPL-2.0-only */ ++/* Copyright (C) 2013 - 2023 Intel Corporation */ ++ ++#ifndef IPU6_FW_COM_H ++#define IPU6_FW_COM_H ++ ++struct ipu6_fw_com_context; ++struct ipu6_bus_device; ++ ++struct ipu6_fw_syscom_queue_config { ++ unsigned int queue_size; /* tokens per queue */ ++ unsigned int token_size; /* bytes per token */ ++}; ++ ++#define SYSCOM_BUTTRESS_FW_PARAMS_ISYS_OFFSET 0 ++ ++struct ipu6_fw_com_cfg { ++ unsigned int num_input_queues; ++ unsigned int num_output_queues; ++ struct ipu6_fw_syscom_queue_config *input; ++ struct ipu6_fw_syscom_queue_config *output; ++ ++ unsigned int dmem_addr; ++ ++ /* firmware-specific configuration data */ ++ void *specific_addr; ++ unsigned int specific_size; ++ int (*cell_ready)(struct ipu6_bus_device *adev); ++ void (*cell_start)(struct ipu6_bus_device *adev); ++ ++ unsigned int buttress_boot_offset; ++}; ++ ++void *ipu6_fw_com_prepare(struct ipu6_fw_com_cfg *cfg, ++ struct ipu6_bus_device *adev, void __iomem *base); ++ ++int ipu6_fw_com_open(struct ipu6_fw_com_context *ctx); ++bool ipu6_fw_com_ready(struct ipu6_fw_com_context *ctx); ++int ipu6_fw_com_close(struct ipu6_fw_com_context *ctx); ++int ipu6_fw_com_release(struct ipu6_fw_com_context *ctx, unsigned int force); ++ ++void *ipu6_recv_get_token(struct ipu6_fw_com_context *ctx, int q_nbr); ++void ipu6_recv_put_token(struct ipu6_fw_com_context *ctx, int q_nbr); ++void *ipu6_send_get_token(struct ipu6_fw_com_context *ctx, int q_nbr); ++void ipu6_send_put_token(struct ipu6_fw_com_context *ctx, int q_nbr); ++ ++#endif +-- +2.43.0 + +From 6784d60f5fe68eca8bfc92785b5badd0bf415048 Mon Sep 17 00:00:00 2001 +From: Bingbu Cao <bingbu.cao@intel.com> +Date: Thu, 11 Jan 2024 14:55:21 +0800 +Subject: [PATCH 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 <bingbu.cao@intel.com> +Link: https://lore.kernel.org/r/20240111065531.2418836-8-bingbu.cao@intel.com +Signed-off-by: Hans de Goede <hdegoede@redhat.com> +--- + drivers/media/pci/intel/ipu6/ipu6-fw-isys.c | 487 +++++++++++++++++ + drivers/media/pci/intel/ipu6/ipu6-fw-isys.h | 573 ++++++++++++++++++++ + 2 files changed, 1060 insertions(+) + create mode 100644 drivers/media/pci/intel/ipu6/ipu6-fw-isys.c + create mode 100644 drivers/media/pci/intel/ipu6/ipu6-fw-isys.h + +diff --git a/drivers/media/pci/intel/ipu6/ipu6-fw-isys.c b/drivers/media/pci/intel/ipu6/ipu6-fw-isys.c +new file mode 100644 +index 000000000000..e06c1c955d38 +--- /dev/null ++++ b/drivers/media/pci/intel/ipu6/ipu6-fw-isys.c +@@ -0,0 +1,487 @@ ++// SPDX-License-Identifier: GPL-2.0-only ++/* ++ * Copyright (C) 2013 - 2023 Intel Corporation ++ */ ++ ++#include <linux/cacheflush.h> ++#include <linux/delay.h> ++#include <linux/device.h> ++#include <linux/io.h> ++#include <linux/spinlock.h> ++#include <linux/types.h> ++ ++#include "ipu6-bus.h" ++#include "ipu6-fw-com.h" ++#include "ipu6-isys.h" ++#include "ipu6-platform-isys-csi2-reg.h" ++#include "ipu6-platform-regs.h" ++ ++static const char send_msg_types[N_IPU6_FW_ISYS_SEND_TYPE][32] = { ++ "STREAM_OPEN", ++ "STREAM_START", ++ "STREAM_START_AND_CAPTURE", ++ "STREAM_CAPTURE", ++ "STREAM_STOP", ++ "STREAM_FLUSH", ++ "STREAM_CLOSE" ++}; ++ ++static int handle_proxy_response(struct ipu6_isys *isys, unsigned int req_id) ++{ ++ struct device *dev = &isys->adev->auxdev.dev; ++ struct ipu6_fw_isys_proxy_resp_info_abi *resp; ++ int ret; ++ ++ resp = ipu6_recv_get_token(isys->fwcom, IPU6_BASE_PROXY_RECV_QUEUES); ++ if (!resp) ++ return 1; ++ ++ dev_dbg(dev, "Proxy response: id %u, error %u, details %u\n", ++ resp->request_id, resp->error_info.error, ++ resp->error_info.error_details); ++ ++ ret = req_id == resp->request_id ? 0 : -EIO; ++ ++ ipu6_recv_put_token(isys->fwcom, IPU6_BASE_PROXY_RECV_QUEUES); ++ ++ return ret; ++} ++ ++int ipu6_fw_isys_send_proxy_token(struct ipu6_isys *isys, ++ unsigned int req_id, ++ unsigned int index, ++ unsigned int offset, u32 value) ++{ ++ struct ipu6_fw_com_context *ctx = isys->fwcom; ++ struct device *dev = &isys->adev->auxdev.dev; ++ struct ipu6_fw_proxy_send_queue_token *token; ++ unsigned int timeout = 1000; ++ int ret; ++ ++ dev_dbg(dev, ++ "proxy send: req_id 0x%x, index %d, offset 0x%x, value 0x%x\n", ++ req_id, index, offset, value); ++ ++ token = ipu6_send_get_token(ctx, IPU6_BASE_PROXY_SEND_QUEUES); ++ if (!token) ++ return -EBUSY; ++ ++ token->request_id = req_id; ++ token->region_index = index; ++ token->offset = offset; ++ token->value = value; ++ ipu6_send_put_token(ctx, IPU6_BASE_PROXY_SEND_QUEUES); ++ ++ do { ++ usleep_range(100, 110); ++ ret = handle_proxy_response(isys, req_id); ++ if (!ret) ++ break; ++ if (ret == -EIO) { ++ dev_err(dev, "Proxy respond with unexpected id\n"); ++ break; ++ } ++ timeout--; ++ } while (ret && timeout); ++ ++ if (!timeout) ++ dev_err(dev, "Proxy response timed out\n"); ++ ++ return ret; ++} ++ ++int ipu6_fw_isys_complex_cmd(struct ipu6_isys *isys, ++ const unsigned int stream_handle, ++ void *cpu_mapped_buf, ++ dma_addr_t dma_mapped_buf, ++ size_t size, u16 send_type) ++{ ++ struct ipu6_fw_com_context *ctx = isys->fwcom; ++ struct device *dev = &isys->adev->auxdev.dev; ++ struct ipu6_fw_send_queue_token *token; ++ ++ if (send_type >= N_IPU6_FW_ISYS_SEND_TYPE) ++ return -EINVAL; ++ ++ dev_dbg(dev, "send_token: %s\n", send_msg_types[send_type]); ++ ++ /* ++ * Time to flush cache in case we have some payload. Not all messages ++ * have that ++ */ ++ if (cpu_mapped_buf) ++ clflush_cache_range(cpu_mapped_buf, size); ++ ++ token = ipu6_send_get_token(ctx, ++ stream_handle + IPU6_BASE_MSG_SEND_QUEUES); ++ if (!token) ++ return -EBUSY; ++ ++ token->payload = dma_mapped_buf; ++ token->buf_handle = (unsigned long)cpu_mapped_buf; ++ token->send_type = send_type; ++ ++ ipu6_send_put_token(ctx, stream_handle + IPU6_BASE_MSG_SEND_QUEUES); ++ ++ return 0; ++} ++ ++int ipu6_fw_isys_simple_cmd(struct ipu6_isys *isys, ++ const unsigned int stream_handle, u16 send_type) ++{ ++ return ipu6_fw_isys_complex_cmd(isys, stream_handle, NULL, 0, 0, ++ send_type); ++} ++ ++int ipu6_fw_isys_close(struct ipu6_isys *isys) ++{ ++ struct device *dev = &isys->adev->auxdev.dev; ++ int retry = IPU6_ISYS_CLOSE_RETRY; ++ unsigned long flags; ++ void *fwcom; ++ int ret; ++ ++ /* ++ * Stop the isys fw. Actual close takes ++ * some time as the FW must stop its actions including code fetch ++ * to SP icache. ++ * spinlock to wait the interrupt handler to be finished ++ */ ++ spin_lock_irqsave(&isys->power_lock, flags); ++ ret = ipu6_fw_com_close(isys->fwcom); ++ fwcom = isys->fwcom; ++ isys->fwcom = NULL; ++ spin_unlock_irqrestore(&isys->power_lock, flags); ++ if (ret) ++ dev_err(dev, "Device close failure: %d\n", ret); ++ ++ /* release probably fails if the close failed. Let's try still */ ++ do { ++ usleep_range(400, 500); ++ ret = ipu6_fw_com_release(fwcom, 0); ++ retry--; ++ } while (ret && retry); ++ ++ if (ret) { ++ dev_err(dev, "Device release time out %d\n", ret); ++ spin_lock_irqsave(&isys->power_lock, flags); ++ isys->fwcom = fwcom; ++ spin_unlock_irqrestore(&isys->power_lock, flags); ++ } ++ ++ return ret; ++} ++ ++void ipu6_fw_isys_cleanup(struct ipu6_isys *isys) ++{ ++ int ret; ++ ++ ret = ipu6_fw_com_release(isys->fwcom, 1); ++ if (ret < 0) ++ dev_warn(&isys->adev->auxdev.dev, ++ "Device busy, fw_com release failed."); ++ isys->fwcom = NULL; ++} ++ ++static void start_sp(struct ipu6_bus_device *adev) ++{ ++ struct ipu6_isys *isys = ipu6_bus_get_drvdata(adev); ++ void __iomem *spc_regs_base = isys->pdata->base + ++ isys->pdata->ipdata->hw_variant.spc_offset; ++ u32 val = IPU6_ISYS_SPC_STATUS_START | ++ IPU6_ISYS_SPC_STATUS_RUN | ++ IPU6_ISYS_SPC_STATUS_CTRL_ICACHE_INVALIDATE; ++ ++ val |= isys->icache_prefetch ? IPU6_ISYS_SPC_STATUS_ICACHE_PREFETCH : 0; ++ ++ writel(val, spc_regs_base + IPU6_ISYS_REG_SPC_STATUS_CTRL); ++} ++ ++static int query_sp(struct ipu6_bus_device *adev) ++{ ++ struct ipu6_isys *isys = ipu6_bus_get_drvdata(adev); ++ void __iomem *spc_regs_base = isys->pdata->base + ++ isys->pdata->ipdata->hw_variant.spc_offset; ++ u32 val; ++ ++ val = readl(spc_regs_base + IPU6_ISYS_REG_SPC_STATUS_CTRL); ++ /* return true when READY == 1, START == 0 */ ++ val &= IPU6_ISYS_SPC_STATUS_READY | IPU6_ISYS_SPC_STATUS_START; ++ ++ return val == IPU6_ISYS_SPC_STATUS_READY; ++} ++ ++static int ipu6_isys_fwcom_cfg_init(struct ipu6_isys *isys, ++ struct ipu6_fw_com_cfg *fwcom, ++ unsigned int num_streams) ++{ ++ unsigned int max_send_queues, max_sram_blocks, max_devq_size; ++ struct ipu6_fw_syscom_queue_config *input_queue_cfg; ++ struct ipu6_fw_syscom_queue_config *output_queue_cfg; ++ struct device *dev = &isys->adev->auxdev.dev; ++ int type_proxy = IPU6_FW_ISYS_QUEUE_TYPE_PROXY; ++ int type_dev = IPU6_FW_ISYS_QUEUE_TYPE_DEV; ++ int type_msg = IPU6_FW_ISYS_QUEUE_TYPE_MSG; ++ int base_dev_send = IPU6_BASE_DEV_SEND_QUEUES; ++ int base_msg_send = IPU6_BASE_MSG_SEND_QUEUES; ++ int base_msg_recv = IPU6_BASE_MSG_RECV_QUEUES; ++ struct ipu6_fw_isys_fw_config *isys_fw_cfg; ++ u32 num_in_message_queues; ++ unsigned int max_streams; ++ unsigned int size; ++ unsigned int i; ++ ++ max_streams = isys->pdata->ipdata->max_streams; ++ max_send_queues = isys->pdata->ipdata->max_send_queues; ++ max_sram_blocks = isys->pdata->ipdata->max_sram_blocks; ++ max_devq_size = isys->pdata->ipdata->max_devq_size; ++ num_in_message_queues = clamp(num_streams, 1U, max_streams); ++ isys_fw_cfg = devm_kzalloc(dev, sizeof(*isys_fw_cfg), GFP_KERNEL); ++ if (!isys_fw_cfg) ++ return -ENOMEM; ++ ++ isys_fw_cfg->num_send_queues[type_proxy] = IPU6_N_MAX_PROXY_SEND_QUEUES; ++ isys_fw_cfg->num_send_queues[type_dev] = IPU6_N_MAX_DEV_SEND_QUEUES; ++ isys_fw_cfg->num_send_queues[type_msg] = num_in_message_queues; ++ isys_fw_cfg->num_recv_queues[type_proxy] = IPU6_N_MAX_PROXY_RECV_QUEUES; ++ /* Common msg/dev return queue */ ++ isys_fw_cfg->num_recv_queues[type_dev] = 0; ++ isys_fw_cfg->num_recv_queues[type_msg] = 1; ++ ++ size = sizeof(*input_queue_cfg) * max_send_queues; ++ input_queue_cfg = devm_kzalloc(dev, size, GFP_KERNEL); ++ if (!input_queue_cfg) ++ return -ENOMEM; ++ ++ size = sizeof(*output_queue_cfg) * IPU6_N_MAX_RECV_QUEUES; ++ output_queue_cfg = devm_kzalloc(dev, size, GFP_KERNEL); ++ if (!output_queue_cfg) ++ return -ENOMEM; ++ ++ fwcom->input = input_queue_cfg; ++ fwcom->output = output_queue_cfg; ++ ++ fwcom->num_input_queues = isys_fw_cfg->num_send_queues[type_proxy] + ++ isys_fw_cfg->num_send_queues[type_dev] + ++ isys_fw_cfg->num_send_queues[type_msg]; ++ ++ fwcom->num_output_queues = isys_fw_cfg->num_recv_queues[type_proxy] + ++ isys_fw_cfg->num_recv_queues[type_dev] + ++ isys_fw_cfg->num_recv_queues[type_msg]; ++ ++ /* SRAM partitioning. Equal partitioning is set. */ ++ for (i = 0; i < max_sram_blocks; i++) { ++ if (i < num_in_message_queues) ++ isys_fw_cfg->buffer_partition.num_gda_pages[i] = ++ (IPU6_DEVICE_GDA_NR_PAGES * ++ IPU6_DEVICE_GDA_VIRT_FACTOR) / ++ num_in_message_queues; ++ else ++ isys_fw_cfg->buffer_partition.num_gda_pages[i] = 0; ++ } ++ ++ /* FW assumes proxy interface at fwcom queue 0 */ ++ for (i = 0; i < isys_fw_cfg->num_send_queues[type_proxy]; i++) { ++ input_queue_cfg[i].token_size = ++ sizeof(struct ipu6_fw_proxy_send_queue_token); ++ input_queue_cfg[i].queue_size = IPU6_ISYS_SIZE_PROXY_SEND_QUEUE; ++ } ++ ++ for (i = 0; i < isys_fw_cfg->num_send_queues[type_dev]; i++) { ++ input_queue_cfg[base_dev_send + i].token_size = ++ sizeof(struct ipu6_fw_send_queue_token); ++ input_queue_cfg[base_dev_send + i].queue_size = max_devq_size; ++ } ++ ++ for (i = 0; i < isys_fw_cfg->num_send_queues[type_msg]; i++) { ++ input_queue_cfg[base_msg_send + i].token_size = ++ sizeof(struct ipu6_fw_send_queue_token); ++ input_queue_cfg[base_msg_send + i].queue_size = ++ IPU6_ISYS_SIZE_SEND_QUEUE; ++ } ++ ++ for (i = 0; i < isys_fw_cfg->num_recv_queues[type_proxy]; i++) { ++ output_queue_cfg[i].token_size = ++ sizeof(struct ipu6_fw_proxy_resp_queue_token); ++ output_queue_cfg[i].queue_size = ++ IPU6_ISYS_SIZE_PROXY_RECV_QUEUE; ++ } ++ /* There is no recv DEV queue */ ++ for (i = 0; i < isys_fw_cfg->num_recv_queues[type_msg]; i++) { ++ output_queue_cfg[base_msg_recv + i].token_size = ++ sizeof(struct ipu6_fw_resp_queue_token); ++ output_queue_cfg[base_msg_recv + i].queue_size = ++ IPU6_ISYS_SIZE_RECV_QUEUE; ++ } ++ ++ fwcom->dmem_addr = isys->pdata->ipdata->hw_variant.dmem_offset; ++ fwcom->specific_addr = isys_fw_cfg; ++ fwcom->specific_size = sizeof(*isys_fw_cfg); ++ ++ return 0; ++} ++ ++int ipu6_fw_isys_init(struct ipu6_isys *isys, unsigned int num_streams) ++{ ++ struct device *dev = &isys->adev->auxdev.dev; ++ int retry = IPU6_ISYS_OPEN_RETRY; ++ struct ipu6_fw_com_cfg fwcom = { ++ .cell_start = start_sp, ++ .cell_ready = query_sp, ++ .buttress_boot_offset = SYSCOM_BUTTRESS_FW_PARAMS_ISYS_OFFSET, ++ }; ++ int ret; ++ ++ ipu6_isys_fwcom_cfg_init(isys, &fwcom, num_streams); ++ ++ isys->fwcom = ipu6_fw_com_prepare(&fwcom, isys->adev, ++ isys->pdata->base); ++ if (!isys->fwcom) { ++ dev_err(dev, "isys fw com prepare failed\n"); ++ return -EIO; ++ } ++ ++ ret = ipu6_fw_com_open(isys->fwcom); ++ if (ret) { ++ dev_err(dev, "isys fw com open failed %d\n", ret); ++ return ret; ++ } ++ ++ do { ++ usleep_range(400, 500); ++ if (ipu6_fw_com_ready(isys->fwcom)) ++ break; ++ retry--; ++ } while (retry > 0); ++ ++ if (!retry) { ++ dev_err(dev, "isys port open ready failed %d\n", ret); ++ ipu6_fw_isys_close(isys); ++ ret = -EIO; ++ } ++ ++ return ret; ++} ++ ++struct ipu6_fw_isys_resp_info_abi * ++ipu6_fw_isys_get_resp(void *context, unsigned int queue) ++{ ++ return ipu6_recv_get_token(context, queue); ++} ++ ++void ipu6_fw_isys_put_resp(void *context, unsigned int queue) ++{ ++ ipu6_recv_put_token(context, queue); ++} ++ ++void ipu6_fw_isys_dump_stream_cfg(struct device *dev, ++ struct ipu6_fw_isys_stream_cfg_data_abi *cfg) ++{ ++ unsigned int i; ++ ++ dev_dbg(dev, "-----------------------------------------------------\n"); ++ dev_dbg(dev, "IPU6_FW_ISYS_STREAM_CFG_DATA\n"); ++ ++ dev_dbg(dev, "compfmt = %d\n", cfg->vc); ++ dev_dbg(dev, "src = %d\n", cfg->src); ++ dev_dbg(dev, "vc = %d\n", cfg->vc); ++ dev_dbg(dev, "isl_use = %d\n", cfg->isl_use); ++ dev_dbg(dev, "sensor_type = %d\n", cfg->sensor_type); ++ ++ dev_dbg(dev, "send_irq_sof_discarded = %d\n", ++ cfg->send_irq_sof_discarded); ++ dev_dbg(dev, "send_irq_eof_discarded = %d\n", ++ cfg->send_irq_eof_discarded); ++ dev_dbg(dev, "send_resp_sof_discarded = %d\n", ++ cfg->send_resp_sof_discarded); ++ dev_dbg(dev, "send_resp_eof_discarded = %d\n", ++ cfg->send_resp_eof_discarded); ++ ++ dev_dbg(dev, "crop:\n"); ++ dev_dbg(dev, "\t.left_top = [%d, %d]\n", cfg->crop.left_offset, ++ cfg->crop.top_offset); ++ dev_dbg(dev, "\t.right_bottom = [%d, %d]\n", cfg->crop.right_offset, ++ cfg->crop.bottom_offset); ++ ++ dev_dbg(dev, "nof_input_pins = %d\n", cfg->nof_input_pins); ++ for (i = 0; i < cfg->nof_input_pins; i++) { ++ dev_dbg(dev, "input pin[%d]:\n", i); ++ dev_dbg(dev, "\t.dt = 0x%0x\n", cfg->input_pins[i].dt); ++ dev_dbg(dev, "\t.mipi_store_mode = %d\n", ++ cfg->input_pins[i].mipi_store_mode); ++ dev_dbg(dev, "\t.bits_per_pix = %d\n", ++ cfg->input_pins[i].bits_per_pix); ++ dev_dbg(dev, "\t.mapped_dt = 0x%0x\n", ++ cfg->input_pins[i].mapped_dt); ++ dev_dbg(dev, "\t.input_res = %dx%d\n", ++ cfg->input_pins[i].input_res.width, ++ cfg->input_pins[i].input_res.height); ++ dev_dbg(dev, "\t.mipi_decompression = %d\n", ++ cfg->input_pins[i].mipi_decompression); ++ dev_dbg(dev, "\t.capture_mode = %d\n", ++ cfg->input_pins[i].capture_mode); ++ } ++ ++ dev_dbg(dev, "nof_output_pins = %d\n", cfg->nof_output_pins); ++ for (i = 0; i < cfg->nof_output_pins; i++) { ++ dev_dbg(dev, "output_pin[%d]:\n", i); ++ dev_dbg(dev, "\t.input_pin_id = %d\n", ++ cfg->output_pins[i].input_pin_id); ++ dev_dbg(dev, "\t.output_res = %dx%d\n", ++ cfg->output_pins[i].output_res.width, ++ cfg->output_pins[i].output_res.height); ++ dev_dbg(dev, "\t.stride = %d\n", cfg->output_pins[i].stride); ++ dev_dbg(dev, "\t.pt = %d\n", cfg->output_pins[i].pt); ++ dev_dbg(dev, "\t.payload_buf_size = %d\n", ++ cfg->output_pins[i].payload_buf_size); ++ dev_dbg(dev, "\t.ft = %d\n", cfg->output_pins[i].ft); ++ dev_dbg(dev, "\t.watermark_in_lines = %d\n", ++ cfg->output_pins[i].watermark_in_lines); ++ dev_dbg(dev, "\t.send_irq = %d\n", ++ cfg->output_pins[i].send_irq); ++ dev_dbg(dev, "\t.reserve_compression = %d\n", ++ cfg->output_pins[i].reserve_compression); ++ dev_dbg(dev, "\t.snoopable = %d\n", ++ cfg->output_pins[i].snoopable); ++ dev_dbg(dev, "\t.error_handling_enable = %d\n", ++ cfg->output_pins[i].error_handling_enable); ++ dev_dbg(dev, "\t.sensor_type = %d\n", ++ cfg->output_pins[i].sensor_type); ++ } ++ dev_dbg(dev, "-----------------------------------------------------\n"); ++} ++ ++void ++ipu6_fw_isys_dump_frame_buff_set(struct device *dev, ++ struct ipu6_fw_isys_frame_buff_set_abi *buf, ++ unsigned int outputs) ++{ ++ unsigned int i; ++ ++ dev_dbg(dev, "-----------------------------------------------------\n"); ++ dev_dbg(dev, "IPU6_FW_ISYS_FRAME_BUFF_SET\n"); ++ ++ for (i = 0; i < outputs; i++) { ++ dev_dbg(dev, "output_pin[%d]:\n", i); ++ dev_dbg(dev, "\t.out_buf_id = %llu\n", ++ buf->output_pins[i].out_buf_id); ++ dev_dbg(dev, "\t.addr = 0x%x\n", buf->output_pins[i].addr); ++ dev_dbg(dev, "\t.compress = %d\n", ++ buf->output_pins[i].compress); ++ } ++ ++ dev_dbg(dev, "send_irq_sof = 0x%x\n", buf->send_irq_sof); ++ dev_dbg(dev, "send_irq_eof = 0x%x\n", buf->send_irq_eof); ++ dev_dbg(dev, "send_resp_sof = 0x%x\n", buf->send_resp_sof); ++ dev_dbg(dev, "send_resp_eof = 0x%x\n", buf->send_resp_eof); ++ dev_dbg(dev, "send_irq_capture_ack = 0x%x\n", ++ buf->send_irq_capture_ack); ++ dev_dbg(dev, "send_irq_capture_done = 0x%x\n", ++ buf->send_irq_capture_done); ++ dev_dbg(dev, "send_resp_capture_ack = 0x%x\n", ++ buf->send_resp_capture_ack); ++ dev_dbg(dev, "send_resp_capture_done = 0x%x\n", ++ buf->send_resp_capture_done); ++ ++ dev_dbg(dev, "-----------------------------------------------------\n"); ++} +diff --git a/drivers/media/pci/intel/ipu6/ipu6-fw-isys.h b/drivers/media/pci/intel/ipu6/ipu6-fw-isys.h +new file mode 100644 +index 000000000000..a7ffa0e22bf0 +--- /dev/null ++++ b/drivers/media/pci/intel/ipu6/ipu6-fw-isys.h +@@ -0,0 +1,573 @@ ++/* SPDX-License-Identifier: GPL-2.0-only */ ++/* Copyright (C) 2013 - 2023 Intel Corporation */ ++ ++#ifndef IPU6_FW_ISYS_H ++#define IPU6_FW_ISYS_H ++ ++#include <linux/types.h> ++ ++struct device; ++struct ipu6_isys; ++ ++/* Max number of Input/Output Pins */ ++#define IPU6_MAX_IPINS 4 ++ ++#define IPU6_MAX_OPINS ((IPU6_MAX_IPINS) + 1) ++ ++#define IPU6_STREAM_ID_MAX 16 ++#define IPU6_NONSECURE_STREAM_ID_MAX 12 ++#define IPU6_DEV_SEND_QUEUE_SIZE (IPU6_STREAM_ID_MAX) ++#define IPU6_NOF_SRAM_BLOCKS_MAX (IPU6_STREAM_ID_MAX) ++#define IPU6_N_MAX_MSG_SEND_QUEUES (IPU6_STREAM_ID_MAX) ++#define IPU6SE_STREAM_ID_MAX 8 ++#define IPU6SE_NONSECURE_STREAM_ID_MAX 4 ++#define IPU6SE_DEV_SEND_QUEUE_SIZE (IPU6SE_STREAM_ID_MAX) ++#define IPU6SE_NOF_SRAM_BLOCKS_MAX (IPU6SE_STREAM_ID_MAX) ++#define IPU6SE_N_MAX_MSG_SEND_QUEUES (IPU6SE_STREAM_ID_MAX) ++ ++/* Single return queue for all streams/commands type */ ++#define IPU6_N_MAX_MSG_RECV_QUEUES 1 ++/* Single device queue for high priority commands (bypass in-order queue) */ ++#define IPU6_N_MAX_DEV_SEND_QUEUES 1 ++/* Single dedicated send queue for proxy interface */ ++#define IPU6_N_MAX_PROXY_SEND_QUEUES 1 ++/* Single dedicated recv queue for proxy interface */ ++#define IPU6_N_MAX_PROXY_RECV_QUEUES 1 ++/* Send queues layout */ ++#define IPU6_BASE_PROXY_SEND_QUEUES 0 ++#define IPU6_BASE_DEV_SEND_QUEUES \ ++ (IPU6_BASE_PROXY_SEND_QUEUES + IPU6_N_MAX_PROXY_SEND_QUEUES) ++#define IPU6_BASE_MSG_SEND_QUEUES \ ++ (IPU6_BASE_DEV_SEND_QUEUES + IPU6_N_MAX_DEV_SEND_QUEUES) ++/* Recv queues layout */ ++#define IPU6_BASE_PROXY_RECV_QUEUES 0 ++#define IPU6_BASE_MSG_RECV_QUEUES \ ++ (IPU6_BASE_PROXY_RECV_QUEUES + IPU6_N_MAX_PROXY_RECV_QUEUES) ++#define IPU6_N_MAX_RECV_QUEUES \ ++ (IPU6_BASE_MSG_RECV_QUEUES + IPU6_N_MAX_MSG_RECV_QUEUES) ++ ++#define IPU6_N_MAX_SEND_QUEUES \ ++ (IPU6_BASE_MSG_SEND_QUEUES + IPU6_N_MAX_MSG_SEND_QUEUES) ++#define IPU6SE_N_MAX_SEND_QUEUES \ ++ (IPU6_BASE_MSG_SEND_QUEUES + IPU6SE_N_MAX_MSG_SEND_QUEUES) ++ ++/* Max number of supported input pins routed in ISL */ ++#define IPU6_MAX_IPINS_IN_ISL 2 ++ ++/* Max number of planes for frame formats supported by the FW */ ++#define IPU6_PIN_PLANES_MAX 4 ++ ++#define IPU6_FW_ISYS_SENSOR_TYPE_START 14 ++#define IPU6_FW_ISYS_SENSOR_TYPE_END 19 ++#define IPU6SE_FW_ISYS_SENSOR_TYPE_START 6 ++#define IPU6SE_FW_ISYS_SENSOR_TYPE_END 11 ++/* ++ * Device close takes some time from last ack message to actual stopping ++ * of the SP processor. As long as the SP processor runs we can't proceed with ++ * clean up of resources. ++ */ ++#define IPU6_ISYS_OPEN_RETRY 2000 ++#define IPU6_ISYS_CLOSE_RETRY 2000 ++#define IPU6_FW_CALL_TIMEOUT_JIFFIES msecs_to_jiffies(2000) ++ ++enum ipu6_fw_isys_resp_type { ++ IPU6_FW_ISYS_RESP_TYPE_STREAM_OPEN_DONE = 0, ++ IPU6_FW_ISYS_RESP_TYPE_STREAM_START_ACK, ++ IPU6_FW_ISYS_RESP_TYPE_STREAM_START_AND_CAPTURE_ACK, ++ IPU6_FW_ISYS_RESP_TYPE_STREAM_CAPTURE_ACK, ++ IPU6_FW_ISYS_RESP_TYPE_STREAM_STOP_ACK, ++ IPU6_FW_ISYS_RESP_TYPE_STREAM_FLUSH_ACK, ++ IPU6_FW_ISYS_RESP_TYPE_STREAM_CLOSE_ACK, ++ IPU6_FW_ISYS_RESP_TYPE_PIN_DATA_READY, ++ IPU6_FW_ISYS_RESP_TYPE_PIN_DATA_WATERMARK, ++ IPU6_FW_ISYS_RESP_TYPE_FRAME_SOF, ++ IPU6_FW_ISYS_RESP_TYPE_FRAME_EOF, ++ IPU6_FW_ISYS_RESP_TYPE_STREAM_START_AND_CAPTURE_DONE, ++ IPU6_FW_ISYS_RESP_TYPE_STREAM_CAPTURE_DONE, ++ IPU6_FW_ISYS_RESP_TYPE_PIN_DATA_SKIPPED, ++ IPU6_FW_ISYS_RESP_TYPE_STREAM_CAPTURE_SKIPPED, ++ IPU6_FW_ISYS_RESP_TYPE_FRAME_SOF_DISCARDED, ++ IPU6_FW_ISYS_RESP_TYPE_FRAME_EOF_DISCARDED, ++ IPU6_FW_ISYS_RESP_TYPE_STATS_DATA_READY, ++ N_IPU6_FW_ISYS_RESP_TYPE ++}; ++ ++enum ipu6_fw_isys_send_type { ++ IPU6_FW_ISYS_SEND_TYPE_STREAM_OPEN = 0, ++ IPU6_FW_ISYS_SEND_TYPE_STREAM_START, ++ IPU6_FW_ISYS_SEND_TYPE_STREAM_START_AND_CAPTURE, ++ IPU6_FW_ISYS_SEND_TYPE_STREAM_CAPTURE, ++ IPU6_FW_ISYS_SEND_TYPE_STREAM_STOP, ++ IPU6_FW_ISYS_SEND_TYPE_STREAM_FLUSH, ++ IPU6_FW_ISYS_SEND_TYPE_STREAM_CLOSE, ++ N_IPU6_FW_ISYS_SEND_TYPE ++}; ++ ++enum ipu6_fw_isys_queue_type { ++ IPU6_FW_ISYS_QUEUE_TYPE_PROXY = 0, ++ IPU6_FW_ISYS_QUEUE_TYPE_DEV, ++ IPU6_FW_ISYS_QUEUE_TYPE_MSG, ++ N_IPU6_FW_ISYS_QUEUE_TYPE ++}; ++ ++enum ipu6_fw_isys_stream_source { ++ IPU6_FW_ISYS_STREAM_SRC_PORT_0 = 0, ++ IPU6_FW_ISYS_STREAM_SRC_PORT_1, ++ IPU6_FW_ISYS_STREAM_SRC_PORT_2, ++ IPU6_FW_ISYS_STREAM_SRC_PORT_3, ++ IPU6_FW_ISYS_STREAM_SRC_PORT_4, ++ IPU6_FW_ISYS_STREAM_SRC_PORT_5, ++ IPU6_FW_ISYS_STREAM_SRC_PORT_6, ++ IPU6_FW_ISYS_STREAM_SRC_PORT_7, ++ IPU6_FW_ISYS_STREAM_SRC_PORT_8, ++ IPU6_FW_ISYS_STREAM_SRC_PORT_9, ++ IPU6_FW_ISYS_STREAM_SRC_PORT_10, ++ IPU6_FW_ISYS_STREAM_SRC_PORT_11, ++ IPU6_FW_ISYS_STREAM_SRC_PORT_12, ++ IPU6_FW_ISYS_STREAM_SRC_PORT_13, ++ IPU6_FW_ISYS_STREAM_SRC_PORT_14, ++ IPU6_FW_ISYS_STREAM_SRC_PORT_15, ++ IPU6_FW_ISYS_STREAM_SRC_MIPIGEN_0, ++ IPU6_FW_ISYS_STREAM_SRC_MIPIGEN_1, ++ IPU6_FW_ISYS_STREAM_SRC_MIPIGEN_2, ++ IPU6_FW_ISYS_STREAM_SRC_MIPIGEN_3, ++ IPU6_FW_ISYS_STREAM_SRC_MIPIGEN_4, ++ IPU6_FW_ISYS_STREAM_SRC_MIPIGEN_5, ++ IPU6_FW_ISYS_STREAM_SRC_MIPIGEN_6, ++ IPU6_FW_ISYS_STREAM_SRC_MIPIGEN_7, ++ IPU6_FW_ISYS_STREAM_SRC_MIPIGEN_8, ++ IPU6_FW_ISYS_STREAM_SRC_MIPIGEN_9, ++ N_IPU6_FW_ISYS_STREAM_SRC ++}; ++ ++#define IPU6_FW_ISYS_STREAM_SRC_CSI2_PORT0 IPU6_FW_ISYS_STREAM_SRC_PORT_0 ++#define IPU6_FW_ISYS_STREAM_SRC_CSI2_PORT1 IPU6_FW_ISYS_STREAM_SRC_PORT_1 ++#define IPU6_FW_ISYS_STREAM_SRC_CSI2_PORT2 IPU6_FW_ISYS_STREAM_SRC_PORT_2 ++#define IPU6_FW_ISYS_STREAM_SRC_CSI2_PORT3 IPU6_FW_ISYS_STREAM_SRC_PORT_3 ++ ++#define IPU6_FW_ISYS_STREAM_SRC_CSI2_3PH_PORTA IPU6_FW_ISYS_STREAM_SRC_PORT_4 ++#define IPU6_FW_ISYS_STREAM_SRC_CSI2_3PH_PORTB IPU6_FW_ISYS_STREAM_SRC_PORT_5 ++#define IPU6_FW_ISYS_STREAM_SRC_CSI2_3PH_CPHY_PORT0 \ ++ IPU6_FW_ISYS_STREAM_SRC_PORT_6 ++#define IPU6_FW_ISYS_STREAM_SRC_CSI2_3PH_CPHY_PORT1 \ ++ IPU6_FW_ISYS_STREAM_SRC_PORT_7 ++#define IPU6_FW_ISYS_STREAM_SRC_CSI2_3PH_CPHY_PORT2 \ ++ IPU6_FW_ISYS_STREAM_SRC_PORT_8 ++#define IPU6_FW_ISYS_STREAM_SRC_CSI2_3PH_CPHY_PORT3 \ ++ IPU6_FW_ISYS_STREAM_SRC_PORT_9 ++ ++#define IPU6_FW_ISYS_STREAM_SRC_MIPIGEN_PORT0 IPU6_FW_ISYS_STREAM_SRC_MIPIGEN_0 ++#define IPU6_FW_ISYS_STREAM_SRC_MIPIGEN_PORT1 IPU6_FW_ISYS_STREAM_SRC_MIPIGEN_1 ++ ++/** ++ * enum ipu6_fw_isys_mipi_vc: MIPI csi2 spec ++ * supports up to 4 virtual per physical channel ++ */ ++enum ipu6_fw_isys_mipi_vc { ++ IPU6_FW_ISYS_MIPI_VC_0 = 0, ++ IPU6_FW_ISYS_MIPI_VC_1, ++ IPU6_FW_ISYS_MIPI_VC_2, ++ IPU6_FW_ISYS_MIPI_VC_3, ++ N_IPU6_FW_ISYS_MIPI_VC ++}; ++ ++enum ipu6_fw_isys_frame_format_type { ++ IPU6_FW_ISYS_FRAME_FORMAT_NV11 = 0, /* 12 bit YUV 411, Y, UV plane */ ++ IPU6_FW_ISYS_FRAME_FORMAT_NV12, /* 12 bit YUV 420, Y, UV plane */ ++ IPU6_FW_ISYS_FRAME_FORMAT_NV12_16, /* 16 bit YUV 420, Y, UV plane */ ++ /* 12 bit YUV 420, Intel proprietary tiled format */ ++ IPU6_FW_ISYS_FRAME_FORMAT_NV12_TILEY, ++ ++ IPU6_FW_ISYS_FRAME_FORMAT_NV16, /* 16 bit YUV 422, Y, UV plane */ ++ IPU6_FW_ISYS_FRAME_FORMAT_NV21, /* 12 bit YUV 420, Y, VU plane */ ++ IPU6_FW_ISYS_FRAME_FORMAT_NV61, /* 16 bit YUV 422, Y, VU plane */ ++ IPU6_FW_ISYS_FRAME_FORMAT_YV12, /* 12 bit YUV 420, Y, V, U plane */ ++ IPU6_FW_ISYS_FRAME_FORMAT_YV16, /* 16 bit YUV 422, Y, V, U plane */ ++ IPU6_FW_ISYS_FRAME_FORMAT_YUV420, /* 12 bit YUV 420, Y, U, V plane */ ++ IPU6_FW_ISYS_FRAME_FORMAT_YUV420_10, /* yuv420, 10 bits per subpixel */ ++ IPU6_FW_ISYS_FRAME_FORMAT_YUV420_12, /* yuv420, 12 bits per subpixel */ ++ IPU6_FW_ISYS_FRAME_FORMAT_YUV420_14, /* yuv420, 14 bits per subpixel */ ++ IPU6_FW_ISYS_FRAME_FORMAT_YUV420_16, /* yuv420, 16 bits per subpixel */ ++ IPU6_FW_ISYS_FRAME_FORMAT_YUV422, /* 16 bit YUV 422, Y, U, V plane */ ++ IPU6_FW_ISYS_FRAME_FORMAT_YUV422_16, /* yuv422, 16 bits per subpixel */ ++ IPU6_FW_ISYS_FRAME_FORMAT_UYVY, /* 16 bit YUV 422, UYVY interleaved */ ++ IPU6_FW_ISYS_FRAME_FORMAT_YUYV, /* 16 bit YUV 422, YUYV interleaved */ ++ IPU6_FW_ISYS_FRAME_FORMAT_YUV444, /* 24 bit YUV 444, Y, U, V plane */ ++ /* Internal format, 2 y lines followed by a uvinterleaved line */ ++ IPU6_FW_ISYS_FRAME_FORMAT_YUV_LINE, ++ IPU6_FW_ISYS_FRAME_FORMAT_RAW8, /* RAW8, 1 plane */ ++ IPU6_FW_ISYS_FRAME_FORMAT_RAW10, /* RAW10, 1 plane */ ++ IPU6_FW_ISYS_FRAME_FORMAT_RAW12, /* RAW12, 1 plane */ ++ IPU6_FW_ISYS_FRAME_FORMAT_RAW14, /* RAW14, 1 plane */ ++ IPU6_FW_ISYS_FRAME_FORMAT_RAW16, /* RAW16, 1 plane */ ++ /** ++ * 16 bit RGB, 1 plane. Each 3 sub pixels are packed into one 16 bit ++ * value, 5 bits for R, 6 bits for G and 5 bits for B. ++ */ ++ IPU6_FW_ISYS_FRAME_FORMAT_RGB565, ++ IPU6_FW_ISYS_FRAME_FORMAT_PLANAR_RGB888, /* 24 bit RGB, 3 planes */ ++ IPU6_FW_ISYS_FRAME_FORMAT_RGBA888, /* 32 bit RGBA, 1 plane, A=Alpha */ ++ IPU6_FW_ISYS_FRAME_FORMAT_QPLANE6, /* Internal, for advanced ISP */ ++ IPU6_FW_ISYS_FRAME_FORMAT_BINARY_8, /* byte stream, used for jpeg. */ ++ N_IPU6_FW_ISYS_FRAME_FORMAT ++}; ++ ++#define IPU6_FW_ISYS_FRAME_FORMAT_RAW (IPU6_FW_ISYS_FRAME_FORMAT_RAW16) ++ ++enum ipu6_fw_isys_pin_type { ++ /* captured as MIPI packets */ ++ IPU6_FW_ISYS_PIN_TYPE_MIPI = 0, ++ /* captured through the SoC path */ ++ IPU6_FW_ISYS_PIN_TYPE_RAW_SOC = 3, ++}; ++ ++/** ++ * enum ipu6_fw_isys_mipi_store_mode. Describes if long MIPI packets reach ++ * MIPI SRAM with the long packet header or ++ * if not, then only option is to capture it with pin type MIPI. ++ */ ++enum ipu6_fw_isys_mipi_store_mode { ++ IPU6_FW_ISYS_MIPI_STORE_MODE_NORMAL = 0, ++ IPU6_FW_ISYS_MIPI_STORE_MODE_DISCARD_LONG_HEADER, ++ N_IPU6_FW_ISYS_MIPI_STORE_MODE ++}; ++ ++enum ipu6_fw_isys_capture_mode { ++ IPU6_FW_ISYS_CAPTURE_MODE_REGULAR = 0, ++ IPU6_FW_ISYS_CAPTURE_MODE_BURST, ++ N_IPU6_FW_ISYS_CAPTURE_MODE, ++}; ++ ++enum ipu6_fw_isys_sensor_mode { ++ IPU6_FW_ISYS_SENSOR_MODE_NORMAL = 0, ++ IPU6_FW_ISYS_SENSOR_MODE_TOBII, ++ N_IPU6_FW_ISYS_SENSOR_MODE, ++}; ++ ++enum ipu6_fw_isys_error { ++ IPU6_FW_ISYS_ERROR_NONE = 0, ++ IPU6_FW_ISYS_ERROR_FW_INTERNAL_CONSISTENCY, ++ IPU6_FW_ISYS_ERROR_HW_CONSISTENCY, ++ IPU6_FW_ISYS_ERROR_DRIVER_INVALID_COMMAND_SEQUENCE, ++ IPU6_FW_ISYS_ERROR_DRIVER_INVALID_DEVICE_CONFIGURATION, ++ IPU6_FW_ISYS_ERROR_DRIVER_INVALID_STREAM_CONFIGURATION, ++ IPU6_FW_ISYS_ERROR_DRIVER_INVALID_FRAME_CONFIGURATION, ++ IPU6_FW_ISYS_ERROR_INSUFFICIENT_RESOURCES, ++ IPU6_FW_ISYS_ERROR_HW_REPORTED_STR2MMIO, ++ IPU6_FW_ISYS_ERROR_HW_REPORTED_SIG2CIO, ++ IPU6_FW_ISYS_ERROR_SENSOR_FW_SYNC, ++ IPU6_FW_ISYS_ERROR_STREAM_IN_SUSPENSION, ++ IPU6_FW_ISYS_ERROR_RESPONSE_QUEUE_FULL, ++ N_IPU6_FW_ISYS_ERROR ++}; ++ ++enum ipu6_fw_proxy_error { ++ IPU6_FW_PROXY_ERROR_NONE = 0, ++ IPU6_FW_PROXY_ERROR_INVALID_WRITE_REGION, ++ IPU6_FW_PROXY_ERROR_INVALID_WRITE_OFFSET, ++ N_IPU6_FW_PROXY_ERROR ++}; ++ ++/* firmware ABI structure below are aligned in firmware, no need pack */ ++struct ipu6_fw_isys_buffer_partition_abi { ++ u32 num_gda_pages[IPU6_STREAM_ID_MAX]; ++}; ++ ++struct ipu6_fw_isys_fw_config { ++ struct ipu6_fw_isys_buffer_partition_abi buffer_partition; ++ u32 num_send_queues[N_IPU6_FW_ISYS_QUEUE_TYPE]; ++ u32 num_recv_queues[N_IPU6_FW_ISYS_QUEUE_TYPE]; ++}; ++ ++/** ++ * struct ipu6_fw_isys_resolution_abi: Generic resolution structure. ++ */ ++struct ipu6_fw_isys_resolution_abi { ++ u32 width; ++ u32 height; ++}; ++ ++/** ++ * struct ipu6_fw_isys_output_pin_payload_abi ++ * @out_buf_id: Points to output pin buffer - buffer identifier ++ * @addr: Points to output pin buffer - CSS Virtual Address ++ * @compress: Request frame compression (1), or not (0) ++ */ ++struct ipu6_fw_isys_output_pin_payload_abi { ++ u64 out_buf_id; ++ u32 addr; ++ u32 compress; ++}; ++ ++/** ++ * struct ipu6_fw_isys_output_pin_info_abi ++ * @output_res: output pin resolution ++ * @stride: output stride in Bytes (not valid for statistics) ++ * @watermark_in_lines: pin watermark level in lines ++ * @payload_buf_size: minimum size in Bytes of all buffers that will be ++ * supplied for capture on this pin ++ * @send_irq: assert if pin event should trigger irq ++ * @pt: pin type -real format "enum ipu6_fw_isys_pin_type" ++ * @ft: frame format type -real format "enum ipu6_fw_isys_frame_format_type" ++ * @input_pin_id: related input pin id ++ * @reserve_compression: reserve compression resources for pin ++ */ ++struct ipu6_fw_isys_output_pin_info_abi { ++ struct ipu6_fw_isys_resolution_abi output_res; ++ u32 stride; ++ u32 watermark_in_lines; ++ u32 payload_buf_size; ++ u32 ts_offsets[IPU6_PIN_PLANES_MAX]; ++ u32 s2m_pixel_soc_pixel_remapping; ++ u32 csi_be_soc_pixel_remapping; ++ u8 send_irq; ++ u8 input_pin_id; ++ u8 pt; ++ u8 ft; ++ u8 reserved; ++ u8 reserve_compression; ++ u8 snoopable; ++ u8 error_handling_enable; ++ u32 sensor_type; ++}; ++ ++/** ++ * struct ipu6_fw_isys_input_pin_info_abi ++ * @input_res: input resolution ++ * @dt: mipi data type ((enum ipu6_fw_isys_mipi_data_type) ++ * @mipi_store_mode: defines if legacy long packet header will be stored or ++ * discarded if discarded, output pin type for this ++ * input pin can only be MIPI ++ * (enum ipu6_fw_isys_mipi_store_mode) ++ * @bits_per_pix: native bits per pixel ++ * @mapped_dt: actual data type from sensor ++ * @mipi_decompression: defines which compression will be in mipi backend ++ * @crop_first_and_last_lines Control whether to crop the ++ * first and last line of the ++ * input image. Crop done by HW ++ * device. ++ * @capture_mode: mode of capture, regular or burst, default value is regular ++ */ ++struct ipu6_fw_isys_input_pin_info_abi { ++ struct ipu6_fw_isys_resolution_abi input_res; ++ u8 dt; ++ u8 mipi_store_mode; ++ u8 bits_per_pix; ++ u8 mapped_dt; ++ u8 mipi_decompression; ++ u8 crop_first_and_last_lines; ++ u8 capture_mode; ++ u8 reserved; ++}; ++ ++/** ++ * struct ipu6_fw_isys_cropping_abi - cropping coordinates ++ */ ++struct ipu6_fw_isys_cropping_abi { ++ s32 top_offset; ++ s32 left_offset; ++ s32 bottom_offset; ++ s32 right_offset; ++}; ++ ++/** ++ * struct ipu6_fw_isys_stream_cfg_data_abi ++ * ISYS stream configuration data structure ++ * @crop: for extended use and is not used in FW currently ++ * @input_pins: input pin descriptors ++ * @output_pins: output pin descriptors ++ * @compfmt: de-compression setting for User Defined Data ++ * @nof_input_pins: number of input pins ++ * @nof_output_pins: number of output pins ++ * @send_irq_sof_discarded: send irq on discarded frame sof response ++ * - if '1' it will override the send_resp_sof_discarded ++ * and send the response ++ * - if '0' the send_resp_sof_discarded will determine ++ * whether to send the response ++ * @send_irq_eof_discarded: send irq on discarded frame eof response ++ * - if '1' it will override the send_resp_eof_discarded ++ * and send the response ++ * - if '0' the send_resp_eof_discarded will determine ++ * whether to send the response ++ * @send_resp_sof_discarded: send response for discarded frame sof detected, ++ * used only when send_irq_sof_discarded is '0' ++ * @send_resp_eof_discarded: send response for discarded frame eof detected, ++ * used only when send_irq_eof_discarded is '0' ++ * @src: Stream source index e.g. MIPI_generator_0, CSI2-rx_1 ++ * @vc: MIPI Virtual Channel (up to 4 virtual per physical channel) ++ * @isl_use: indicates whether stream requires ISL and how ++ * @sensor_type: type of connected sensor, tobii or others, default is 0 ++ */ ++struct ipu6_fw_isys_stream_cfg_data_abi { ++ struct ipu6_fw_isys_cropping_abi crop; ++ struct ipu6_fw_isys_input_pin_info_abi input_pins[IPU6_MAX_IPINS]; ++ struct ipu6_fw_isys_output_pin_info_abi output_pins[IPU6_MAX_OPINS]; ++ u32 compfmt; ++ u8 nof_input_pins; ++ u8 nof_output_pins; ++ u8 send_irq_sof_discarded; ++ u8 send_irq_eof_discarded; ++ u8 send_resp_sof_discarded; ++ u8 send_resp_eof_discarded; ++ u8 src; ++ u8 vc; ++ u8 isl_use; ++ u8 sensor_type; ++ u8 reserved; ++ u8 reserved2; ++}; ++ ++/** ++ * struct ipu6_fw_isys_frame_buff_set - frame buffer set ++ * @output_pins: output pin addresses ++ * @send_irq_sof: send irq on frame sof response ++ * - if '1' it will override the send_resp_sof and ++ * send the response ++ * - if '0' the send_resp_sof will determine whether to ++ * send the response ++ * @send_irq_eof: send irq on frame eof response ++ * - if '1' it will override the send_resp_eof and ++ * send the response ++ * - if '0' the send_resp_eof will determine whether to ++ * send the response ++ * @send_resp_sof: send response for frame sof detected, ++ * used only when send_irq_sof is '0' ++ * @send_resp_eof: send response for frame eof detected, ++ * used only when send_irq_eof is '0' ++ * @send_resp_capture_ack: send response for capture ack event ++ * @send_resp_capture_done: send response for capture done event ++ */ ++struct ipu6_fw_isys_frame_buff_set_abi { ++ struct ipu6_fw_isys_output_pin_payload_abi output_pins[IPU6_MAX_OPINS]; ++ u8 send_irq_sof; ++ u8 send_irq_eof; ++ u8 send_irq_capture_ack; ++ u8 send_irq_capture_done; ++ u8 send_resp_sof; ++ u8 send_resp_eof; ++ u8 send_resp_capture_ack; ++ u8 send_resp_capture_done; ++ u8 reserved[8]; ++}; ++ ++/** ++ * struct ipu6_fw_isys_error_info_abi ++ * @error: error code if something went wrong ++ * @error_details: depending on error code, it may contain additional error info ++ */ ++struct ipu6_fw_isys_error_info_abi { ++ u32 error; ++ u32 error_details; ++}; ++ ++/** ++ * struct ipu6_fw_isys_resp_info_comm ++ * @pin: this var is only valid for pin event related responses, ++ * contains pin addresses ++ * @error_info: error information from the FW ++ * @timestamp: Time information for event if available ++ * @stream_handle: stream id the response corresponds to ++ * @type: response type (enum ipu6_fw_isys_resp_type) ++ * @pin_id: pin id that the pin payload corresponds to ++ */ ++struct ipu6_fw_isys_resp_info_abi { ++ u64 buf_id; ++ struct ipu6_fw_isys_output_pin_payload_abi pin; ++ struct ipu6_fw_isys_error_info_abi error_info; ++ u32 timestamp[2]; ++ u8 stream_handle; ++ u8 type; ++ u8 pin_id; ++ u8 reserved; ++ u32 reserved2; ++}; ++ ++/** ++ * struct ipu6_fw_isys_proxy_error_info_comm ++ * @proxy_error: error code if something went wrong ++ * @proxy_error_details: depending on error code, it may contain additional ++ * error info ++ */ ++struct ipu6_fw_isys_proxy_error_info_abi { ++ u32 error; ++ u32 error_details; ++}; ++ ++struct ipu6_fw_isys_proxy_resp_info_abi { ++ u32 request_id; ++ struct ipu6_fw_isys_proxy_error_info_abi error_info; ++}; ++ ++/** ++ * struct ipu6_fw_proxy_write_queue_token ++ * @request_id: update id for the specific proxy write request ++ * @region_index: Region id for the proxy write request ++ * @offset: Offset of the write request according to the base address ++ * of the region ++ * @value: Value that is requested to be written with the proxy write request ++ */ ++struct ipu6_fw_proxy_write_queue_token { ++ u32 request_id; ++ u32 region_index; ++ u32 offset; ++ u32 value; ++}; ++ ++/** ++ * struct ipu6_fw_resp_queue_token ++ */ ++struct ipu6_fw_resp_queue_token { ++ struct ipu6_fw_isys_resp_info_abi resp_info; ++}; ++ ++/** ++ * struct ipu6_fw_send_queue_token ++ */ ++struct ipu6_fw_send_queue_token { ++ u64 buf_handle; ++ u32 payload; ++ u16 send_type; ++ u16 stream_id; ++}; ++ ++/** ++ * struct ipu6_fw_proxy_resp_queue_token ++ */ ++struct ipu6_fw_proxy_resp_queue_token { ++ struct ipu6_fw_isys_proxy_resp_info_abi proxy_resp_info; ++}; ++ ++/** ++ * struct ipu6_fw_proxy_send_queue_token ++ */ ++struct ipu6_fw_proxy_send_queue_token { ++ u32 request_id; ++ u32 region_index; ++ u32 offset; ++ u32 value; ++}; ++ ++void ++ipu6_fw_isys_dump_stream_cfg(struct device *dev, ++ struct ipu6_fw_isys_stream_cfg_data_abi *cfg); ++void ++ipu6_fw_isys_dump_frame_buff_set(struct device *dev, ++ struct ipu6_fw_isys_frame_buff_set_abi *buf, ++ unsigned int outputs); ++int ipu6_fw_isys_init(struct ipu6_isys *isys, unsigned int num_streams); ++int ipu6_fw_isys_close(struct ipu6_isys *isys); ++int ipu6_fw_isys_simple_cmd(struct ipu6_isys *isys, ++ const unsigned int stream_handle, u16 send_type); ++int ipu6_fw_isys_complex_cmd(struct ipu6_isys *isys, ++ const unsigned int stream_handle, ++ void *cpu_mapped_buf, dma_addr_t dma_mapped_buf, ++ size_t size, u16 send_type); ++int ipu6_fw_isys_send_proxy_token(struct ipu6_isys *isys, ++ unsigned int req_id, ++ unsigned int index, ++ unsigned int offset, u32 value); ++void ipu6_fw_isys_cleanup(struct ipu6_isys *isys); ++struct ipu6_fw_isys_resp_info_abi * ++ipu6_fw_isys_get_resp(void *context, unsigned int queue); ++void ipu6_fw_isys_put_resp(void *context, unsigned int queue); ++#endif +-- +2.43.0 + +From 2d13721450bd1d8e778c0727a7c3eac6cc1fd6c0 Mon Sep 17 00:00:00 2001 +From: Bingbu Cao <bingbu.cao@intel.com> +Date: Thu, 11 Jan 2024 14:55:22 +0800 +Subject: [PATCH 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 <bingbu.cao@intel.com> +Link: https://lore.kernel.org/r/20240111065531.2418836-9-bingbu.cao@intel.com +Signed-off-by: Hans de Goede <hdegoede@redhat.com> +--- + drivers/media/pci/intel/ipu6/ipu6-isys-csi2.c | 666 ++++++++++++++++++ + drivers/media/pci/intel/ipu6/ipu6-isys-csi2.h | 81 +++ + .../media/pci/intel/ipu6/ipu6-isys-subdev.c | 381 ++++++++++ + .../media/pci/intel/ipu6/ipu6-isys-subdev.h | 61 ++ + .../intel/ipu6/ipu6-platform-isys-csi2-reg.h | 189 +++++ + 5 files changed, 1378 insertions(+) + create mode 100644 drivers/media/pci/intel/ipu6/ipu6-isys-csi2.c + create mode 100644 drivers/media/pci/intel/ipu6/ipu6-isys-csi2.h + create mode 100644 drivers/media/pci/intel/ipu6/ipu6-isys-subdev.c + create mode 100644 drivers/media/pci/intel/ipu6/ipu6-isys-subdev.h + create mode 100644 drivers/media/pci/intel/ipu6/ipu6-platform-isys-csi2-reg.h + +diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys-csi2.c b/drivers/media/pci/intel/ipu6/ipu6-isys-csi2.c +new file mode 100644 +index 000000000000..ac9fa3e0d7ab +--- /dev/null ++++ b/drivers/media/pci/intel/ipu6/ipu6-isys-csi2.c +@@ -0,0 +1,666 @@ ++// SPDX-License-Identifier: GPL-2.0-only ++/* ++ * Copyright (C) 2013 - 2023 Intel Corporation ++ */ ++ ++#include <linux/atomic.h> ++#include <linux/bitfield.h> ++#include <linux/bits.h> ++#include <linux/delay.h> ++#include <linux/device.h> ++#include <linux/err.h> ++#include <linux/io.h> ++#include <linux/minmax.h> ++#include <linux/sprintf.h> ++ ++#include <media/media-entity.h> ++#include <media/v4l2-ctrls.h> ++#include <media/v4l2-device.h> ++#include <media/v4l2-event.h> ++#include <media/v4l2-subdev.h> ++ ++#include "ipu6-bus.h" ++#include "ipu6-isys.h" ++#include "ipu6-isys-csi2.h" ++#include "ipu6-isys-subdev.h" ++#include "ipu6-platform-isys-csi2-reg.h" ++ ++static const u32 csi2_supported_codes[] = { ++ MEDIA_BUS_FMT_RGB565_1X16, ++ MEDIA_BUS_FMT_RGB888_1X24, ++ MEDIA_BUS_FMT_UYVY8_1X16, ++ MEDIA_BUS_FMT_YUYV8_1X16, ++ MEDIA_BUS_FMT_SBGGR10_1X10, ++ MEDIA_BUS_FMT_SGBRG10_1X10, ++ MEDIA_BUS_FMT_SGRBG10_1X10, ++ MEDIA_BUS_FMT_SRGGB10_1X10, ++ MEDIA_BUS_FMT_SBGGR12_1X12, ++ MEDIA_BUS_FMT_SGBRG12_1X12, ++ MEDIA_BUS_FMT_SGRBG12_1X12, ++ MEDIA_BUS_FMT_SRGGB12_1X12, ++ MEDIA_BUS_FMT_SBGGR8_1X8, ++ MEDIA_BUS_FMT_SGBRG8_1X8, ++ MEDIA_BUS_FMT_SGRBG8_1X8, ++ MEDIA_BUS_FMT_SRGGB8_1X8, ++ 0 ++}; ++ ++/* ++ * Strings corresponding to CSI-2 receiver errors are here. ++ * Corresponding macros are defined in the header file. ++ */ ++static const struct ipu6_csi2_error dphy_rx_errors[] = { ++ { "Single packet header error corrected", true }, ++ { "Multiple packet header errors detected", true }, ++ { "Payload checksum (CRC) error", true }, ++ { "Transfer FIFO overflow", false }, ++ { "Reserved short packet data type detected", true }, ++ { "Reserved long packet data type detected", true }, ++ { "Incomplete long packet detected", false }, ++ { "Frame sync error", false }, ++ { "Line sync error", false }, ++ { "DPHY recoverable synchronization error", true }, ++ { "DPHY fatal error", false }, ++ { "DPHY elastic FIFO overflow", false }, ++ { "Inter-frame short packet discarded", true }, ++ { "Inter-frame long packet discarded", true }, ++ { "MIPI pktgen overflow", false }, ++ { "MIPI pktgen data loss", false }, ++ { "FIFO overflow", false }, ++ { "Lane deskew", false }, ++ { "SOT sync error", false }, ++ { "HSIDLE detected", false } ++}; ++ ++s64 ipu6_isys_csi2_get_link_freq(struct ipu6_isys_csi2 *csi2) ++{ ++ struct media_pad *src_pad; ++ struct v4l2_subdev *ext_sd; ++ struct device *dev; ++ ++ if (!csi2) ++ return -EINVAL; ++ ++ dev = &csi2->isys->adev->auxdev.dev; ++ src_pad = media_entity_remote_source_pad_unique(&csi2->asd.sd.entity); ++ if (IS_ERR_OR_NULL(src_pad)) { ++ dev_err(dev, "can't get source pad of %s\n", csi2->asd.sd.name); ++ return -ENOLINK; ++ } ++ ++ ext_sd = media_entity_to_v4l2_subdev(src_pad->entity); ++ if (WARN(!ext_sd, "Failed to get subdev for %s\n", csi2->asd.sd.name)) ++ return -ENODEV; ++ ++ return v4l2_get_link_freq(ext_sd->ctrl_handler, 0, 0); ++} ++ ++static int csi2_subscribe_event(struct v4l2_subdev *sd, struct v4l2_fh *fh, ++ struct v4l2_event_subscription *sub) ++{ ++ struct ipu6_isys_subdev *asd = to_ipu6_isys_subdev(sd); ++ struct ipu6_isys_csi2 *csi2 = to_ipu6_isys_csi2(asd); ++ struct device *dev = &csi2->isys->adev->auxdev.dev; ++ ++ dev_dbg(dev, "csi2 subscribe event(type %u id %u)\n", ++ sub->type, sub->id); ++ ++ switch (sub->type) { ++ case V4L2_EVENT_FRAME_SYNC: ++ return v4l2_event_subscribe(fh, sub, 10, NULL); ++ case V4L2_EVENT_CTRL: ++ return v4l2_ctrl_subscribe_event(fh, sub); ++ default: ++ return -EINVAL; ++ } ++} ++ ++static const struct v4l2_subdev_core_ops csi2_sd_core_ops = { ++ .subscribe_event = csi2_subscribe_event, ++ .unsubscribe_event = v4l2_event_subdev_unsubscribe, ++}; ++ ++/* ++ * The input system CSI2+ receiver has several ++ * parameters affecting the receiver timings. These depend ++ * on the MIPI bus frequency F in Hz (sensor transmitter rate) ++ * as follows: ++ * register value = (A/1e9 + B * UI) / COUNT_ACC ++ * where ++ * UI = 1 / (2 * F) in seconds ++ * COUNT_ACC = counter accuracy in seconds ++ * COUNT_ACC = 0.125 ns = 1 / 8 ns, ACCINV = 8. ++ * ++ * A and B are coefficients from the table below, ++ * depending whether the register minimum or maximum value is ++ * calculated. ++ * Minimum Maximum ++ * Clock lane A B A B ++ * reg_rx_csi_dly_cnt_termen_clane 0 0 38 0 ++ * reg_rx_csi_dly_cnt_settle_clane 95 -8 300 -16 ++ * Data lanes ++ * reg_rx_csi_dly_cnt_termen_dlane0 0 0 35 4 ++ * reg_rx_csi_dly_cnt_settle_dlane0 85 -2 145 -6 ++ * reg_rx_csi_dly_cnt_termen_dlane1 0 0 35 4 ++ * reg_rx_csi_dly_cnt_settle_dlane1 85 -2 145 -6 ++ * reg_rx_csi_dly_cnt_termen_dlane2 0 0 35 4 ++ * reg_rx_csi_dly_cnt_settle_dlane2 85 -2 145 -6 ++ * reg_rx_csi_dly_cnt_termen_dlane3 0 0 35 4 ++ * reg_rx_csi_dly_cnt_settle_dlane3 85 -2 145 -6 ++ * ++ * We use the minimum values of both A and B. ++ */ ++ ++#define DIV_SHIFT 8 ++#define CSI2_ACCINV 8 ++ ++static u32 calc_timing(s32 a, s32 b, s64 link_freq, s32 accinv) ++{ ++ return accinv * a + (accinv * b * (500000000 >> DIV_SHIFT) ++ / (s32)(link_freq >> DIV_SHIFT)); ++} ++ ++static int ++ipu6_isys_csi2_calc_timing(struct ipu6_isys_csi2 *csi2, ++ struct ipu6_isys_csi2_timing *timing, s32 accinv) ++{ ++ struct device *dev = &csi2->isys->adev->auxdev.dev; ++ s64 link_freq; ++ ++ link_freq = ipu6_isys_csi2_get_link_freq(csi2); ++ if (link_freq < 0) ++ return link_freq; ++ ++ timing->ctermen = calc_timing(CSI2_CSI_RX_DLY_CNT_TERMEN_CLANE_A, ++ CSI2_CSI_RX_DLY_CNT_TERMEN_CLANE_B, ++ link_freq, accinv); ++ timing->csettle = calc_timing(CSI2_CSI_RX_DLY_CNT_SETTLE_CLANE_A, ++ CSI2_CSI_RX_DLY_CNT_SETTLE_CLANE_B, ++ link_freq, accinv); ++ timing->dtermen = calc_timing(CSI2_CSI_RX_DLY_CNT_TERMEN_DLANE_A, ++ CSI2_CSI_RX_DLY_CNT_TERMEN_DLANE_B, ++ link_freq, accinv); ++ timing->dsettle = calc_timing(CSI2_CSI_RX_DLY_CNT_SETTLE_DLANE_A, ++ CSI2_CSI_RX_DLY_CNT_SETTLE_DLANE_B, ++ link_freq, accinv); ++ ++ dev_dbg(dev, "ctermen %u csettle %u dtermen %u dsettle %u\n", ++ timing->ctermen, timing->csettle, ++ timing->dtermen, timing->dsettle); ++ ++ return 0; ++} ++ ++void ipu6_isys_register_errors(struct ipu6_isys_csi2 *csi2) ++{ ++ u32 irq = readl(csi2->base + CSI_PORT_REG_BASE_IRQ_CSI + ++ CSI_PORT_REG_BASE_IRQ_STATUS_OFFSET); ++ struct ipu6_isys *isys = csi2->isys; ++ u32 mask; ++ ++ mask = isys->pdata->ipdata->csi2.irq_mask; ++ writel(irq & mask, csi2->base + CSI_PORT_REG_BASE_IRQ_CSI + ++ CSI_PORT_REG_BASE_IRQ_CLEAR_OFFSET); ++ csi2->receiver_errors |= irq & mask; ++} ++ ++void ipu6_isys_csi2_error(struct ipu6_isys_csi2 *csi2) ++{ ++ struct device *dev = &csi2->isys->adev->auxdev.dev; ++ const struct ipu6_csi2_error *errors; ++ u32 status; ++ u32 i; ++ ++ /* register errors once more in case of interrupts are disabled */ ++ ipu6_isys_register_errors(csi2); ++ status = csi2->receiver_errors; ++ csi2->receiver_errors = 0; ++ errors = dphy_rx_errors; ++ ++ for (i = 0; i < CSI_RX_NUM_ERRORS_IN_IRQ; i++) { ++ if (status & BIT(i)) ++ dev_err_ratelimited(dev, "csi2-%i error: %s\n", ++ csi2->port, errors[i].error_string); ++ } ++} ++ ++static int ipu6_isys_csi2_set_stream(struct v4l2_subdev *sd, ++ const struct ipu6_isys_csi2_timing *timing, ++ unsigned int nlanes, int enable) ++{ ++ struct ipu6_isys_subdev *asd = to_ipu6_isys_subdev(sd); ++ struct ipu6_isys_csi2 *csi2 = to_ipu6_isys_csi2(asd); ++ struct ipu6_isys *isys = csi2->isys; ++ struct device *dev = &isys->adev->auxdev.dev; ++ struct ipu6_isys_csi2_config cfg; ++ unsigned int nports; ++ int ret = 0; ++ u32 mask = 0; ++ u32 i; ++ ++ dev_dbg(dev, "stream %s CSI2-%u with %u lanes\n", enable ? "on" : "off", ++ csi2->port, nlanes); ++ ++ cfg.port = csi2->port; ++ cfg.nlanes = nlanes; ++ ++ mask = isys->pdata->ipdata->csi2.irq_mask; ++ nports = isys->pdata->ipdata->csi2.nports; ++ ++ if (!enable) { ++ writel(0, csi2->base + CSI_REG_CSI_FE_ENABLE); ++ writel(0, csi2->base + CSI_REG_PPI2CSI_ENABLE); ++ ++ writel(0, ++ csi2->base + CSI_PORT_REG_BASE_IRQ_CSI + ++ CSI_PORT_REG_BASE_IRQ_ENABLE_OFFSET); ++ writel(mask, ++ csi2->base + CSI_PORT_REG_BASE_IRQ_CSI + ++ CSI_PORT_REG_BASE_IRQ_CLEAR_OFFSET); ++ writel(0, ++ csi2->base + CSI_PORT_REG_BASE_IRQ_CSI_SYNC + ++ CSI_PORT_REG_BASE_IRQ_ENABLE_OFFSET); ++ writel(0xffffffff, ++ csi2->base + CSI_PORT_REG_BASE_IRQ_CSI_SYNC + ++ CSI_PORT_REG_BASE_IRQ_CLEAR_OFFSET); ++ ++ isys->phy_set_power(isys, &cfg, timing, false); ++ ++ writel(0, isys->pdata->base + CSI_REG_HUB_FW_ACCESS_PORT ++ (isys->pdata->ipdata->csi2.fw_access_port_ofs, ++ csi2->port)); ++ writel(0, isys->pdata->base + ++ CSI_REG_HUB_DRV_ACCESS_PORT(csi2->port)); ++ ++ return ret; ++ } ++ ++ /* reset port reset */ ++ writel(0x1, csi2->base + CSI_REG_PORT_GPREG_SRST); ++ usleep_range(100, 200); ++ writel(0x0, csi2->base + CSI_REG_PORT_GPREG_SRST); ++ ++ /* enable port clock */ ++ for (i = 0; i < nports; i++) { ++ writel(1, isys->pdata->base + CSI_REG_HUB_DRV_ACCESS_PORT(i)); ++ writel(1, isys->pdata->base + CSI_REG_HUB_FW_ACCESS_PORT ++ (isys->pdata->ipdata->csi2.fw_access_port_ofs, i)); ++ } ++ ++ /* enable all error related irq */ ++ writel(mask, ++ csi2->base + CSI_PORT_REG_BASE_IRQ_CSI + ++ CSI_PORT_REG_BASE_IRQ_STATUS_OFFSET); ++ writel(mask, ++ csi2->base + CSI_PORT_REG_BASE_IRQ_CSI + ++ CSI_PORT_REG_BASE_IRQ_MASK_OFFSET); ++ writel(mask, ++ csi2->base + CSI_PORT_REG_BASE_IRQ_CSI + ++ CSI_PORT_REG_BASE_IRQ_CLEAR_OFFSET); ++ writel(mask, ++ csi2->base + CSI_PORT_REG_BASE_IRQ_CSI + ++ CSI_PORT_REG_BASE_IRQ_LEVEL_NOT_PULSE_OFFSET); ++ writel(mask, ++ csi2->base + CSI_PORT_REG_BASE_IRQ_CSI + ++ CSI_PORT_REG_BASE_IRQ_ENABLE_OFFSET); ++ ++ /* ++ * Using event from firmware instead of irq to handle CSI2 sync event ++ * which can reduce system wakeups. If CSI2 sync irq enabled, we need ++ * disable the firmware CSI2 sync event to avoid duplicate handling. ++ */ ++ writel(0xffffffff, csi2->base + CSI_PORT_REG_BASE_IRQ_CSI_SYNC + ++ CSI_PORT_REG_BASE_IRQ_STATUS_OFFSET); ++ writel(0, csi2->base + CSI_PORT_REG_BASE_IRQ_CSI_SYNC + ++ CSI_PORT_REG_BASE_IRQ_MASK_OFFSET); ++ writel(0xffffffff, csi2->base + CSI_PORT_REG_BASE_IRQ_CSI_SYNC + ++ CSI_PORT_REG_BASE_IRQ_CLEAR_OFFSET); ++ writel(0, csi2->base + CSI_PORT_REG_BASE_IRQ_CSI_SYNC + ++ CSI_PORT_REG_BASE_IRQ_LEVEL_NOT_PULSE_OFFSET); ++ writel(0xffffffff, csi2->base + CSI_PORT_REG_BASE_IRQ_CSI_SYNC + ++ CSI_PORT_REG_BASE_IRQ_ENABLE_OFFSET); ++ ++ /* configure to enable FE and PPI2CSI */ ++ writel(0, csi2->base + CSI_REG_CSI_FE_MODE); ++ writel(CSI_SENSOR_INPUT, csi2->base + CSI_REG_CSI_FE_MUX_CTRL); ++ writel(CSI_CNTR_SENSOR_LINE_ID | CSI_CNTR_SENSOR_FRAME_ID, ++ csi2->base + CSI_REG_CSI_FE_SYNC_CNTR_SEL); ++ writel(FIELD_PREP(PPI_INTF_CONFIG_NOF_ENABLED_DLANES_MASK, nlanes - 1), ++ csi2->base + CSI_REG_PPI2CSI_CONFIG_PPI_INTF); ++ ++ writel(1, csi2->base + CSI_REG_PPI2CSI_ENABLE); ++ writel(1, csi2->base + CSI_REG_CSI_FE_ENABLE); ++ ++ ret = isys->phy_set_power(isys, &cfg, timing, true); ++ if (ret) ++ dev_err(dev, "csi-%d phy power up failed %d\n", csi2->port, ++ ret); ++ ++ return ret; ++} ++ ++static int set_stream(struct v4l2_subdev *sd, int enable) ++{ ++ struct ipu6_isys_subdev *asd = to_ipu6_isys_subdev(sd); ++ struct ipu6_isys_csi2 *csi2 = to_ipu6_isys_csi2(asd); ++ struct device *dev = &csi2->isys->adev->auxdev.dev; ++ struct ipu6_isys_csi2_timing timing = { }; ++ unsigned int nlanes; ++ int ret; ++ ++ dev_dbg(dev, "csi2 stream %s callback\n", enable ? "on" : "off"); ++ ++ if (!enable) { ++ csi2->stream_count--; ++ if (csi2->stream_count) ++ return 0; ++ ++ ipu6_isys_csi2_set_stream(sd, &timing, 0, enable); ++ return 0; ++ } ++ ++ if (csi2->stream_count) { ++ csi2->stream_count++; ++ return 0; ++ } ++ ++ nlanes = csi2->nlanes; ++ ++ ret = ipu6_isys_csi2_calc_timing(csi2, &timing, CSI2_ACCINV); ++ if (ret) ++ return ret; ++ ++ ret = ipu6_isys_csi2_set_stream(sd, &timing, nlanes, enable); ++ if (ret) ++ return ret; ++ ++ csi2->stream_count++; ++ ++ return 0; ++} ++ ++static int ipu6_isys_csi2_set_sel(struct v4l2_subdev *sd, ++ struct v4l2_subdev_state *state, ++ struct v4l2_subdev_selection *sel) ++{ ++ struct ipu6_isys_subdev *asd = to_ipu6_isys_subdev(sd); ++ struct device *dev = &asd->isys->adev->auxdev.dev; ++ struct v4l2_mbus_framefmt *sink_ffmt; ++ struct v4l2_mbus_framefmt *src_ffmt; ++ struct v4l2_rect *crop; ++ ++ if (sel->pad == CSI2_PAD_SINK || sel->target != V4L2_SEL_TGT_CROP) ++ return -EINVAL; ++ ++ sink_ffmt = v4l2_subdev_state_get_opposite_stream_format(state, ++ sel->pad, ++ sel->stream); ++ if (!sink_ffmt) ++ return -EINVAL; ++ ++ src_ffmt = v4l2_subdev_state_get_stream_format(state, sel->pad, ++ sel->stream); ++ if (!src_ffmt) ++ return -EINVAL; ++ ++ crop = v4l2_subdev_state_get_stream_crop(state, sel->pad, sel->stream); ++ if (!crop) ++ return -EINVAL; ++ ++ /* Only vertical cropping is supported */ ++ sel->r.left = 0; ++ sel->r.width = sink_ffmt->width; ++ /* Non-bayer formats can't be single line cropped */ ++ if (!ipu6_isys_is_bayer_format(sink_ffmt->code)) ++ sel->r.top &= ~1; ++ sel->r.height = clamp(sel->r.height & ~1, IPU6_ISYS_MIN_HEIGHT, ++ sink_ffmt->height - sel->r.top); ++ *crop = sel->r; ++ ++ /* update source pad format */ ++ src_ffmt->width = sel->r.width; ++ src_ffmt->height = sel->r.height; ++ if (ipu6_isys_is_bayer_format(sink_ffmt->code)) ++ src_ffmt->code = ipu6_isys_convert_bayer_order(sink_ffmt->code, ++ sel->r.left, ++ sel->r.top); ++ dev_dbg(dev, "set crop for %s sel: %d,%d,%d,%d code: 0x%x\n", ++ sd->name, sel->r.left, sel->r.top, sel->r.width, sel->r.height, ++ src_ffmt->code); ++ ++ return 0; ++} ++ ++static int ipu6_isys_csi2_get_sel(struct v4l2_subdev *sd, ++ struct v4l2_subdev_state *state, ++ struct v4l2_subdev_selection *sel) ++{ ++ struct v4l2_mbus_framefmt *sink_ffmt; ++ struct v4l2_rect *crop; ++ int ret = 0; ++ ++ if (sd->entity.pads[sel->pad].flags & MEDIA_PAD_FL_SINK) ++ return -EINVAL; ++ ++ sink_ffmt = v4l2_subdev_state_get_opposite_stream_format(state, ++ sel->pad, ++ sel->stream); ++ if (!sink_ffmt) ++ return -EINVAL; ++ ++ crop = v4l2_subdev_state_get_stream_crop(state, sel->pad, sel->stream); ++ if (!crop) ++ return -EINVAL; ++ ++ switch (sel->target) { ++ case V4L2_SEL_TGT_CROP_DEFAULT: ++ case V4L2_SEL_TGT_CROP_BOUNDS: ++ sel->r.left = 0; ++ sel->r.top = 0; ++ sel->r.width = sink_ffmt->width; ++ sel->r.height = sink_ffmt->height; ++ break; ++ case V4L2_SEL_TGT_CROP: ++ sel->r = *crop; ++ break; ++ default: ++ ret = -EINVAL; ++ } ++ ++ return ret; ++} ++ ++static const struct v4l2_subdev_video_ops csi2_sd_video_ops = { ++ .s_stream = set_stream, ++}; ++ ++static const struct v4l2_subdev_pad_ops csi2_sd_pad_ops = { ++ .init_cfg = ipu6_isys_subdev_init_cfg, ++ .get_fmt = v4l2_subdev_get_fmt, ++ .set_fmt = ipu6_isys_subdev_set_fmt, ++ .get_selection = ipu6_isys_csi2_get_sel, ++ .set_selection = ipu6_isys_csi2_set_sel, ++ .enum_mbus_code = ipu6_isys_subdev_enum_mbus_code, ++ .set_routing = ipu6_isys_subdev_set_routing, ++}; ++ ++static const struct v4l2_subdev_ops csi2_sd_ops = { ++ .core = &csi2_sd_core_ops, ++ .video = &csi2_sd_video_ops, ++ .pad = &csi2_sd_pad_ops, ++}; ++ ++static const struct media_entity_operations csi2_entity_ops = { ++ .link_validate = v4l2_subdev_link_validate, ++ .has_pad_interdep = v4l2_subdev_has_pad_interdep, ++}; ++ ++void ipu6_isys_csi2_cleanup(struct ipu6_isys_csi2 *csi2) ++{ ++ if (!csi2->isys) ++ return; ++ ++ v4l2_device_unregister_subdev(&csi2->asd.sd); ++ v4l2_subdev_cleanup(&csi2->asd.sd); ++ ipu6_isys_subdev_cleanup(&csi2->asd); ++ csi2->isys = NULL; ++} ++ ++int ipu6_isys_csi2_init(struct ipu6_isys_csi2 *csi2, ++ struct ipu6_isys *isys, ++ void __iomem *base, unsigned int index) ++{ ++ struct device *dev = &isys->adev->auxdev.dev; ++ int ret; ++ ++ csi2->isys = isys; ++ csi2->base = base; ++ csi2->port = index; ++ ++ csi2->asd.sd.entity.ops = &csi2_entity_ops; ++ csi2->asd.isys = isys; ++ ret = ipu6_isys_subdev_init(&csi2->asd, &csi2_sd_ops, 0, ++ NR_OF_CSI2_SINK_PADS, NR_OF_CSI2_SRC_PADS); ++ if (ret) ++ goto fail; ++ ++ csi2->asd.source = IPU6_FW_ISYS_STREAM_SRC_CSI2_PORT0 + index; ++ csi2->asd.supported_codes = csi2_supported_codes; ++ snprintf(csi2->asd.sd.name, sizeof(csi2->asd.sd.name), ++ IPU6_ISYS_ENTITY_PREFIX " CSI2 %u", index); ++ v4l2_set_subdevdata(&csi2->asd.sd, &csi2->asd); ++ ret = v4l2_subdev_init_finalize(&csi2->asd.sd); ++ if (ret) { ++ dev_err(dev, "failed to init v4l2 subdev\n"); ++ goto fail; ++ } ++ ++ ret = v4l2_device_register_subdev(&isys->v4l2_dev, &csi2->asd.sd); ++ if (ret) { ++ dev_err(dev, "failed to register v4l2 subdev\n"); ++ goto fail; ++ } ++ ++ return 0; ++ ++fail: ++ ipu6_isys_csi2_cleanup(csi2); ++ ++ return ret; ++} ++ ++void ipu6_isys_csi2_sof_event_by_stream(struct ipu6_isys_stream *stream) ++{ ++ struct video_device *vdev = stream->asd->sd.devnode; ++ struct device *dev = &stream->isys->adev->auxdev.dev; ++ struct ipu6_isys_csi2 *csi2 = ipu6_isys_subdev_to_csi2(stream->asd); ++ struct v4l2_event ev = { ++ .type = V4L2_EVENT_FRAME_SYNC, ++ }; ++ ++ ev.u.frame_sync.frame_sequence = atomic_fetch_inc(&stream->sequence); ++ v4l2_event_queue(vdev, &ev); ++ ++ dev_dbg(dev, "sof_event::csi2-%i sequence: %i, vc: %d\n", ++ csi2->port, ev.u.frame_sync.frame_sequence, stream->vc); ++} ++ ++void ipu6_isys_csi2_eof_event_by_stream(struct ipu6_isys_stream *stream) ++{ ++ struct device *dev = &stream->isys->adev->auxdev.dev; ++ struct ipu6_isys_csi2 *csi2 = ipu6_isys_subdev_to_csi2(stream->asd); ++ u32 frame_sequence = atomic_read(&stream->sequence); ++ ++ dev_dbg(dev, "eof_event::csi2-%i sequence: %i\n", ++ csi2->port, frame_sequence); ++} ++ ++int ipu6_isys_csi2_get_remote_desc(u32 source_stream, ++ struct ipu6_isys_csi2 *csi2, ++ struct media_entity *source_entity, ++ struct v4l2_mbus_frame_desc_entry *entry, ++ int *nr_queues) ++{ ++ struct v4l2_mbus_frame_desc_entry *desc_entry = NULL; ++ struct device *dev = &csi2->isys->adev->auxdev.dev; ++ struct v4l2_mbus_frame_desc desc; ++ struct v4l2_subdev *source; ++ struct media_pad *pad; ++ unsigned int i; ++ int count = 0; ++ int ret; ++ ++ source = media_entity_to_v4l2_subdev(source_entity); ++ if (!source) ++ return -EPIPE; ++ ++ pad = media_pad_remote_pad_first(&csi2->asd.pad[CSI2_PAD_SINK]); ++ if (!pad) ++ return -EPIPE; ++ ++ ret = v4l2_subdev_call(source, pad, get_frame_desc, pad->index, &desc); ++ if (ret) ++ return ret; ++ ++ if (desc.type != V4L2_MBUS_FRAME_DESC_TYPE_CSI2) { ++ dev_err(dev, "Unsupported frame descriptor type\n"); ++ return -EINVAL; ++ } ++ ++ for (i = 0; i < desc.num_entries; i++) { ++ if (source_stream == desc.entry[i].stream) { ++ desc_entry = &desc.entry[i]; ++ break; ++ } ++ } ++ ++ if (!desc_entry) { ++ dev_err(dev, "Failed to find stream %u from remote subdev\n", ++ source_stream); ++ return -EINVAL; ++ } ++ ++ if (desc_entry->bus.csi2.vc >= NR_OF_CSI2_VC) { ++ dev_err(dev, "invalid vc %d\n", desc_entry->bus.csi2.vc); ++ return -EINVAL; ++ } ++ ++ *entry = *desc_entry; ++ for (i = 0; i < desc.num_entries; i++) { ++ if (entry->bus.csi2.vc == desc.entry[i].bus.csi2.vc) ++ count++; ++ } ++ ++ *nr_queues = count; ++ return 0; ++} ++ ++void ipu6_isys_set_csi2_streams_status(struct ipu6_isys_video *av, bool status) ++{ ++ struct ipu6_isys_stream *stream = av->stream; ++ struct v4l2_subdev *sd = &stream->asd->sd; ++ struct v4l2_subdev_state *state; ++ struct media_pad *r_pad; ++ unsigned int i; ++ u32 r_stream; ++ ++ r_pad = media_pad_remote_pad_first(&av->pad); ++ r_stream = ipu6_isys_get_src_stream_by_src_pad(sd, r_pad->index); ++ ++ state = v4l2_subdev_lock_and_get_active_state(sd); ++ ++ for (i = 0; i < state->stream_configs.num_configs; i++) { ++ struct v4l2_subdev_stream_config *cfg = ++ &state->stream_configs.configs[i]; ++ ++ if (cfg->pad == r_pad->index && r_stream == cfg->stream) { ++ dev_dbg(&av->isys->adev->auxdev.dev, ++ "%s: pad:%u, stream:%u, status:%u\n", ++ sd->entity.name, r_pad->index, r_stream, ++ status); ++ cfg->enabled = status; ++ } ++ } ++ ++ v4l2_subdev_unlock_state(state); ++} +diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys-csi2.h b/drivers/media/pci/intel/ipu6/ipu6-isys-csi2.h +new file mode 100644 +index 000000000000..d4765bae6112 +--- /dev/null ++++ b/drivers/media/pci/intel/ipu6/ipu6-isys-csi2.h +@@ -0,0 +1,81 @@ ++/* SPDX-License-Identifier: GPL-2.0-only */ ++/* Copyright (C) 2013 - 2023 Intel Corporation */ ++ ++#ifndef IPU6_ISYS_CSI2_H ++#define IPU6_ISYS_CSI2_H ++ ++#include <linux/container_of.h> ++ ++#include "ipu6-isys-subdev.h" ++ ++struct media_entity; ++struct v4l2_mbus_frame_desc_entry; ++ ++struct ipu6_isys_video; ++struct ipu6_isys; ++struct ipu6_isys_csi2_pdata; ++struct ipu6_isys_stream; ++ ++#define NR_OF_CSI2_VC 16 ++#define INVALID_VC_ID -1 ++#define NR_OF_CSI2_SINK_PADS 1 ++#define CSI2_PAD_SINK 0 ++#define NR_OF_CSI2_SRC_PADS 8 ++#define CSI2_PAD_SRC 1 ++#define NR_OF_CSI2_PADS (NR_OF_CSI2_SINK_PADS + NR_OF_CSI2_SRC_PADS) ++ ++#define CSI2_CSI_RX_DLY_CNT_TERMEN_CLANE_A 0 ++#define CSI2_CSI_RX_DLY_CNT_TERMEN_CLANE_B 0 ++#define CSI2_CSI_RX_DLY_CNT_SETTLE_CLANE_A 95 ++#define CSI2_CSI_RX_DLY_CNT_SETTLE_CLANE_B -8 ++ ++#define CSI2_CSI_RX_DLY_CNT_TERMEN_DLANE_A 0 ++#define CSI2_CSI_RX_DLY_CNT_TERMEN_DLANE_B 0 ++#define CSI2_CSI_RX_DLY_CNT_SETTLE_DLANE_A 85 ++#define CSI2_CSI_RX_DLY_CNT_SETTLE_DLANE_B -2 ++ ++struct ipu6_isys_csi2 { ++ struct ipu6_isys_subdev asd; ++ struct ipu6_isys_csi2_pdata *pdata; ++ struct ipu6_isys *isys; ++ ++ void __iomem *base; ++ u32 receiver_errors; ++ unsigned int nlanes; ++ unsigned int port; ++ unsigned int stream_count; ++}; ++ ++struct ipu6_isys_csi2_timing { ++ u32 ctermen; ++ u32 csettle; ++ u32 dtermen; ++ u32 dsettle; ++}; ++ ++struct ipu6_csi2_error { ++ const char *error_string; ++ bool is_info_only; ++}; ++ ++#define ipu6_isys_subdev_to_csi2(__sd) \ ++ container_of(__sd, struct ipu6_isys_csi2, asd) ++ ++#define to_ipu6_isys_csi2(__asd) container_of(__asd, struct ipu6_isys_csi2, asd) ++ ++s64 ipu6_isys_csi2_get_link_freq(struct ipu6_isys_csi2 *csi2); ++int ipu6_isys_csi2_init(struct ipu6_isys_csi2 *csi2, struct ipu6_isys *isys, ++ void __iomem *base, unsigned int index); ++void ipu6_isys_csi2_cleanup(struct ipu6_isys_csi2 *csi2); ++void ipu6_isys_csi2_sof_event_by_stream(struct ipu6_isys_stream *stream); ++void ipu6_isys_csi2_eof_event_by_stream(struct ipu6_isys_stream *stream); ++void ipu6_isys_register_errors(struct ipu6_isys_csi2 *csi2); ++void ipu6_isys_csi2_error(struct ipu6_isys_csi2 *csi2); ++int ipu6_isys_csi2_get_remote_desc(u32 source_stream, ++ struct ipu6_isys_csi2 *csi2, ++ struct media_entity *source_entity, ++ struct v4l2_mbus_frame_desc_entry *entry, ++ int *nr_queues); ++void ipu6_isys_set_csi2_streams_status(struct ipu6_isys_video *av, bool status); ++ ++#endif /* IPU6_ISYS_CSI2_H */ +diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys-subdev.c b/drivers/media/pci/intel/ipu6/ipu6-isys-subdev.c +new file mode 100644 +index 000000000000..510c5ca34f9f +--- /dev/null ++++ b/drivers/media/pci/intel/ipu6/ipu6-isys-subdev.c +@@ -0,0 +1,381 @@ ++// SPDX-License-Identifier: GPL-2.0-only ++/* ++ * Copyright (C) 2013 - 2023 Intel Corporation ++ */ ++ ++#include <linux/bug.h> ++#include <linux/device.h> ++#include <linux/minmax.h> ++ ++#include <media/media-entity.h> ++#include <media/mipi-csi2.h> ++#include <media/v4l2-ctrls.h> ++#include <media/v4l2-subdev.h> ++ ++#include "ipu6-bus.h" ++#include "ipu6-isys.h" ++#include "ipu6-isys-subdev.h" ++ ++unsigned int ipu6_isys_mbus_code_to_bpp(u32 code) ++{ ++ switch (code) { ++ case MEDIA_BUS_FMT_RGB888_1X24: ++ return 24; ++ case MEDIA_BUS_FMT_RGB565_1X16: ++ case MEDIA_BUS_FMT_UYVY8_1X16: ++ case MEDIA_BUS_FMT_YUYV8_1X16: ++ return 16; ++ case MEDIA_BUS_FMT_SBGGR12_1X12: ++ case MEDIA_BUS_FMT_SGBRG12_1X12: ++ case MEDIA_BUS_FMT_SGRBG12_1X12: ++ case MEDIA_BUS_FMT_SRGGB12_1X12: ++ return 12; ++ case MEDIA_BUS_FMT_SBGGR10_1X10: ++ case MEDIA_BUS_FMT_SGBRG10_1X10: ++ case MEDIA_BUS_FMT_SGRBG10_1X10: ++ case MEDIA_BUS_FMT_SRGGB10_1X10: ++ return 10; ++ case MEDIA_BUS_FMT_SBGGR8_1X8: ++ case MEDIA_BUS_FMT_SGBRG8_1X8: ++ case MEDIA_BUS_FMT_SGRBG8_1X8: ++ case MEDIA_BUS_FMT_SRGGB8_1X8: ++ return 8; ++ default: ++ WARN_ON(1); ++ return 8; ++ } ++} ++ ++unsigned int ipu6_isys_mbus_code_to_mipi(u32 code) ++{ ++ switch (code) { ++ case MEDIA_BUS_FMT_RGB565_1X16: ++ return MIPI_CSI2_DT_RGB565; ++ case MEDIA_BUS_FMT_RGB888_1X24: ++ return MIPI_CSI2_DT_RGB888; ++ case MEDIA_BUS_FMT_UYVY8_1X16: ++ case MEDIA_BUS_FMT_YUYV8_1X16: ++ return MIPI_CSI2_DT_YUV422_8B; ++ case MEDIA_BUS_FMT_SBGGR12_1X12: ++ case MEDIA_BUS_FMT_SGBRG12_1X12: ++ case MEDIA_BUS_FMT_SGRBG12_1X12: ++ case MEDIA_BUS_FMT_SRGGB12_1X12: ++ return MIPI_CSI2_DT_RAW12; ++ case MEDIA_BUS_FMT_SBGGR10_1X10: ++ case MEDIA_BUS_FMT_SGBRG10_1X10: ++ case MEDIA_BUS_FMT_SGRBG10_1X10: ++ case MEDIA_BUS_FMT_SRGGB10_1X10: ++ return MIPI_CSI2_DT_RAW10; ++ case MEDIA_BUS_FMT_SBGGR8_1X8: ++ case MEDIA_BUS_FMT_SGBRG8_1X8: ++ case MEDIA_BUS_FMT_SGRBG8_1X8: ++ case MEDIA_BUS_FMT_SRGGB8_1X8: ++ return MIPI_CSI2_DT_RAW8; ++ default: ++ /* return unavailable MIPI data type - 0x3f */ ++ WARN_ON(1); ++ return 0x3f; ++ } ++} ++ ++bool ipu6_isys_is_bayer_format(u32 code) ++{ ++ switch (ipu6_isys_mbus_code_to_mipi(code)) { ++ case MIPI_CSI2_DT_RAW8: ++ case MIPI_CSI2_DT_RAW10: ++ case MIPI_CSI2_DT_RAW12: ++ return true; ++ default: ++ return false; ++ } ++} ++ ++u32 ipu6_isys_convert_bayer_order(u32 code, int x, int y) ++{ ++ static const u32 code_map[] = { ++ MEDIA_BUS_FMT_SRGGB8_1X8, ++ MEDIA_BUS_FMT_SGRBG8_1X8, ++ MEDIA_BUS_FMT_SGBRG8_1X8, ++ MEDIA_BUS_FMT_SBGGR8_1X8, ++ MEDIA_BUS_FMT_SRGGB10_1X10, ++ MEDIA_BUS_FMT_SGRBG10_1X10, ++ MEDIA_BUS_FMT_SGBRG10_1X10, ++ MEDIA_BUS_FMT_SBGGR10_1X10, ++ MEDIA_BUS_FMT_SRGGB12_1X12, ++ MEDIA_BUS_FMT_SGRBG12_1X12, ++ MEDIA_BUS_FMT_SGBRG12_1X12, ++ MEDIA_BUS_FMT_SBGGR12_1X12 ++ }; ++ u32 i; ++ ++ for (i = 0; i < ARRAY_SIZE(code_map); i++) ++ if (code_map[i] == code) ++ break; ++ ++ if (WARN_ON(i == ARRAY_SIZE(code_map))) ++ return code; ++ ++ return code_map[i ^ (((y & 1) << 1) | (x & 1))]; ++} ++ ++int ipu6_isys_subdev_set_fmt(struct v4l2_subdev *sd, ++ struct v4l2_subdev_state *state, ++ struct v4l2_subdev_format *format) ++{ ++ struct ipu6_isys_subdev *asd = to_ipu6_isys_subdev(sd); ++ struct v4l2_mbus_framefmt *fmt; ++ struct v4l2_rect *crop; ++ u32 code = asd->supported_codes[0]; ++ u32 other_pad, other_stream; ++ unsigned int i; ++ int ret; ++ ++ /* No transcoding, source and sink formats must match. */ ++ if ((sd->entity.pads[format->pad].flags & MEDIA_PAD_FL_SOURCE) && ++ sd->entity.num_pads > 1) ++ return v4l2_subdev_get_fmt(sd, state, format); ++ ++ format->format.width = clamp(format->format.width, IPU6_ISYS_MIN_WIDTH, ++ IPU6_ISYS_MAX_WIDTH); ++ format->format.height = clamp(format->format.height, ++ IPU6_ISYS_MIN_HEIGHT, ++ IPU6_ISYS_MAX_HEIGHT); ++ ++ for (i = 0; asd->supported_codes[i]; i++) { ++ if (asd->supported_codes[i] == format->format.code) { ++ code = asd->supported_codes[i]; ++ break; ++ } ++ } ++ format->format.code = code; ++ format->format.field = V4L2_FIELD_NONE; ++ ++ /* Store the format and propagate it to the source pad. */ ++ fmt = v4l2_subdev_state_get_stream_format(state, format->pad, ++ format->stream); ++ if (!fmt) ++ return -EINVAL; ++ ++ *fmt = format->format; ++ ++ if (!(sd->entity.pads[format->pad].flags & MEDIA_PAD_FL_SINK)) ++ return 0; ++ ++ /* propagate format to following source pad */ ++ fmt = v4l2_subdev_state_get_opposite_stream_format(state, format->pad, ++ format->stream); ++ if (!fmt) ++ return -EINVAL; ++ ++ *fmt = format->format; ++ ++ ret = v4l2_subdev_routing_find_opposite_end(&state->routing, ++ format->pad, ++ format->stream, ++ &other_pad, ++ &other_stream); ++ if (ret) ++ return -EINVAL; ++ ++ crop = v4l2_subdev_state_get_stream_crop(state, other_pad, ++ other_stream); ++ /* reset crop */ ++ crop->left = 0; ++ crop->top = 0; ++ crop->width = fmt->width; ++ crop->height = fmt->height; ++ ++ return 0; ++} ++ ++int ipu6_isys_subdev_enum_mbus_code(struct v4l2_subdev *sd, ++ struct v4l2_subdev_state *state, ++ struct v4l2_subdev_mbus_code_enum *code) ++{ ++ struct ipu6_isys_subdev *asd = to_ipu6_isys_subdev(sd); ++ const u32 *supported_codes = asd->supported_codes; ++ u32 index; ++ ++ for (index = 0; supported_codes[index]; index++) { ++ if (index == code->index) { ++ code->code = supported_codes[index]; ++ return 0; ++ } ++ } ++ ++ return -EINVAL; ++} ++ ++static int subdev_set_routing(struct v4l2_subdev *sd, ++ struct v4l2_subdev_state *state, ++ struct v4l2_subdev_krouting *routing) ++{ ++ static const struct v4l2_mbus_framefmt format = { ++ .width = 4096, ++ .height = 3072, ++ .code = MEDIA_BUS_FMT_SGRBG10_1X10, ++ .field = V4L2_FIELD_NONE, ++ }; ++ int ret; ++ ++ ret = v4l2_subdev_routing_validate(sd, routing, ++ V4L2_SUBDEV_ROUTING_ONLY_1_TO_1); ++ if (ret) ++ return ret; ++ ++ return v4l2_subdev_set_routing_with_fmt(sd, state, routing, &format); ++} ++ ++int ipu6_isys_get_stream_pad_fmt(struct v4l2_subdev *sd, u32 pad, u32 stream, ++ struct v4l2_mbus_framefmt *format) ++{ ++ struct v4l2_mbus_framefmt *fmt; ++ struct v4l2_subdev_state *state; ++ ++ if (!sd || !format) ++ return -EINVAL; ++ ++ state = v4l2_subdev_lock_and_get_active_state(sd); ++ fmt = v4l2_subdev_state_get_stream_format(state, pad, stream); ++ if (fmt) ++ *format = *fmt; ++ v4l2_subdev_unlock_state(state); ++ ++ return fmt ? 0 : -EINVAL; ++} ++ ++int ipu6_isys_get_stream_pad_crop(struct v4l2_subdev *sd, u32 pad, u32 stream, ++ struct v4l2_rect *crop) ++{ ++ struct v4l2_subdev_state *state; ++ struct v4l2_rect *rect; ++ ++ if (!sd || !crop) ++ return -EINVAL; ++ ++ state = v4l2_subdev_lock_and_get_active_state(sd); ++ rect = v4l2_subdev_state_get_stream_crop(state, pad, stream); ++ if (rect) ++ *crop = *rect; ++ v4l2_subdev_unlock_state(state); ++ ++ return rect ? 0 : -EINVAL; ++} ++ ++u32 ipu6_isys_get_src_stream_by_src_pad(struct v4l2_subdev *sd, u32 pad) ++{ ++ struct v4l2_subdev_state *state; ++ struct v4l2_subdev_route *routes; ++ unsigned int i; ++ u32 source_stream = 0; ++ ++ state = v4l2_subdev_lock_and_get_active_state(sd); ++ if (!state) ++ return 0; ++ ++ routes = state->routing.routes; ++ for (i = 0; i < state->routing.num_routes; i++) { ++ if (routes[i].source_pad == pad) { ++ source_stream = routes[i].source_stream; ++ break; ++ } ++ } ++ ++ v4l2_subdev_unlock_state(state); ++ ++ return source_stream; ++} ++ ++int ipu6_isys_subdev_init_cfg(struct v4l2_subdev *sd, ++ struct v4l2_subdev_state *state) ++{ ++ struct v4l2_subdev_route route = { ++ .sink_pad = 0, ++ .sink_stream = 0, ++ .source_pad = 1, ++ .source_stream = 0, ++ .flags = V4L2_SUBDEV_ROUTE_FL_ACTIVE, ++ }; ++ struct v4l2_subdev_krouting routing = { ++ .num_routes = 1, ++ .routes = &route, ++ }; ++ ++ return subdev_set_routing(sd, state, &routing); ++} ++ ++int ipu6_isys_subdev_set_routing(struct v4l2_subdev *sd, ++ struct v4l2_subdev_state *state, ++ enum v4l2_subdev_format_whence which, ++ struct v4l2_subdev_krouting *routing) ++{ ++ return subdev_set_routing(sd, state, routing); ++} ++ ++int ipu6_isys_subdev_init(struct ipu6_isys_subdev *asd, ++ const struct v4l2_subdev_ops *ops, ++ unsigned int nr_ctrls, ++ unsigned int num_sink_pads, ++ unsigned int num_source_pads) ++{ ++ unsigned int num_pads = num_sink_pads + num_source_pads; ++ unsigned int i; ++ int ret; ++ ++ v4l2_subdev_init(&asd->sd, ops); ++ ++ asd->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE | ++ V4L2_SUBDEV_FL_HAS_EVENTS | ++ V4L2_SUBDEV_FL_STREAMS; ++ asd->sd.owner = THIS_MODULE; ++ asd->sd.entity.function = MEDIA_ENT_F_VID_IF_BRIDGE; ++ ++ asd->pad = devm_kcalloc(&asd->isys->adev->auxdev.dev, num_pads, ++ sizeof(*asd->pad), GFP_KERNEL); ++ ++ if (!asd->pad) ++ return -ENOMEM; ++ ++ for (i = 0; i < num_sink_pads; i++) ++ asd->pad[i].flags = MEDIA_PAD_FL_SINK | ++ MEDIA_PAD_FL_MUST_CONNECT; ++ ++ for (i = num_sink_pads; i < num_pads; i++) ++ asd->pad[i].flags = MEDIA_PAD_FL_SOURCE; ++ ++ ret = media_entity_pads_init(&asd->sd.entity, num_pads, asd->pad); ++ if (ret) ++ return ret; ++ ++ if (asd->ctrl_init) { ++ ret = v4l2_ctrl_handler_init(&asd->ctrl_handler, nr_ctrls); ++ if (ret) ++ goto out_media_entity_cleanup; ++ ++ asd->ctrl_init(&asd->sd); ++ if (asd->ctrl_handler.error) { ++ ret = asd->ctrl_handler.error; ++ goto out_v4l2_ctrl_handler_free; ++ } ++ ++ asd->sd.ctrl_handler = &asd->ctrl_handler; ++ } ++ ++ asd->source = -1; ++ ++ return 0; ++ ++out_v4l2_ctrl_handler_free: ++ v4l2_ctrl_handler_free(&asd->ctrl_handler); ++ ++out_media_entity_cleanup: ++ media_entity_cleanup(&asd->sd.entity); ++ ++ return ret; ++} ++ ++void ipu6_isys_subdev_cleanup(struct ipu6_isys_subdev *asd) ++{ ++ media_entity_cleanup(&asd->sd.entity); ++ v4l2_ctrl_handler_free(&asd->ctrl_handler); ++} +diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys-subdev.h b/drivers/media/pci/intel/ipu6/ipu6-isys-subdev.h +new file mode 100644 +index 000000000000..adea2a55761d +--- /dev/null ++++ b/drivers/media/pci/intel/ipu6/ipu6-isys-subdev.h +@@ -0,0 +1,61 @@ ++/* SPDX-License-Identifier: GPL-2.0-only */ ++/* Copyright (C) 2013 - 2023 Intel Corporation */ ++ ++#ifndef IPU6_ISYS_SUBDEV_H ++#define IPU6_ISYS_SUBDEV_H ++ ++#include <linux/container_of.h> ++ ++#include <media/media-entity.h> ++#include <media/v4l2-ctrls.h> ++#include <media/v4l2-subdev.h> ++ ++struct ipu6_isys; ++ ++struct ipu6_isys_subdev { ++ struct v4l2_subdev sd; ++ struct ipu6_isys *isys; ++ u32 const *supported_codes; ++ struct media_pad *pad; ++ struct v4l2_ctrl_handler ctrl_handler; ++ void (*ctrl_init)(struct v4l2_subdev *sd); ++ int source; /* SSI stream source; -1 if unset */ ++}; ++ ++#define to_ipu6_isys_subdev(__sd) \ ++ container_of(__sd, struct ipu6_isys_subdev, sd) ++ ++unsigned int ipu6_isys_mbus_code_to_bpp(u32 code); ++unsigned int ipu6_isys_mbus_code_to_mipi(u32 code); ++bool ipu6_isys_is_bayer_format(u32 code); ++u32 ipu6_isys_convert_bayer_order(u32 code, int x, int y); ++ ++int ipu6_isys_subdev_set_fmt(struct v4l2_subdev *sd, ++ struct v4l2_subdev_state *state, ++ struct v4l2_subdev_format *fmt); ++int ipu6_isys_subdev_enum_mbus_code(struct v4l2_subdev *sd, ++ struct v4l2_subdev_state *state, ++ struct v4l2_subdev_mbus_code_enum ++ *code); ++int ipu6_isys_subdev_link_validate(struct v4l2_subdev *sd, ++ struct media_link *link, ++ struct v4l2_subdev_format *source_fmt, ++ struct v4l2_subdev_format *sink_fmt); ++u32 ipu6_isys_get_src_stream_by_src_pad(struct v4l2_subdev *sd, u32 pad); ++int ipu6_isys_get_stream_pad_fmt(struct v4l2_subdev *sd, u32 pad, u32 stream, ++ struct v4l2_mbus_framefmt *format); ++int ipu6_isys_get_stream_pad_crop(struct v4l2_subdev *sd, u32 pad, u32 stream, ++ struct v4l2_rect *crop); ++int ipu6_isys_subdev_init_cfg(struct v4l2_subdev *sd, ++ struct v4l2_subdev_state *state); ++int ipu6_isys_subdev_set_routing(struct v4l2_subdev *sd, ++ struct v4l2_subdev_state *state, ++ enum v4l2_subdev_format_whence which, ++ struct v4l2_subdev_krouting *routing); ++int ipu6_isys_subdev_init(struct ipu6_isys_subdev *asd, ++ const struct v4l2_subdev_ops *ops, ++ unsigned int nr_ctrls, ++ unsigned int num_sink_pads, ++ unsigned int num_source_pads); ++void ipu6_isys_subdev_cleanup(struct ipu6_isys_subdev *asd); ++#endif /* IPU6_ISYS_SUBDEV_H */ +diff --git a/drivers/media/pci/intel/ipu6/ipu6-platform-isys-csi2-reg.h b/drivers/media/pci/intel/ipu6/ipu6-platform-isys-csi2-reg.h +new file mode 100644 +index 000000000000..2034e1109d98 +--- /dev/null ++++ b/drivers/media/pci/intel/ipu6/ipu6-platform-isys-csi2-reg.h +@@ -0,0 +1,189 @@ ++/* SPDX-License-Identifier: GPL-2.0-only */ ++/* Copyright (C) 2023 Intel Corporation */ ++ ++#ifndef IPU6_PLATFORM_ISYS_CSI2_REG_H ++#define IPU6_PLATFORM_ISYS_CSI2_REG_H ++ ++#include <linux/bits.h> ++ ++#define CSI_REG_BASE 0x220000 ++#define CSI_REG_BASE_PORT(id) ((id) * 0x1000) ++ ++#define IPU6_CSI_PORT_A_ADDR_OFFSET \ ++ (CSI_REG_BASE + CSI_REG_BASE_PORT(0)) ++#define IPU6_CSI_PORT_B_ADDR_OFFSET \ ++ (CSI_REG_BASE + CSI_REG_BASE_PORT(1)) ++#define IPU6_CSI_PORT_C_ADDR_OFFSET \ ++ (CSI_REG_BASE + CSI_REG_BASE_PORT(2)) ++#define IPU6_CSI_PORT_D_ADDR_OFFSET \ ++ (CSI_REG_BASE + CSI_REG_BASE_PORT(3)) ++#define IPU6_CSI_PORT_E_ADDR_OFFSET \ ++ (CSI_REG_BASE + CSI_REG_BASE_PORT(4)) ++#define IPU6_CSI_PORT_F_ADDR_OFFSET \ ++ (CSI_REG_BASE + CSI_REG_BASE_PORT(5)) ++#define IPU6_CSI_PORT_G_ADDR_OFFSET \ ++ (CSI_REG_BASE + CSI_REG_BASE_PORT(6)) ++#define IPU6_CSI_PORT_H_ADDR_OFFSET \ ++ (CSI_REG_BASE + CSI_REG_BASE_PORT(7)) ++ ++/* CSI Port Genral Purpose Registers */ ++#define CSI_REG_PORT_GPREG_SRST 0x0 ++#define CSI_REG_PORT_GPREG_CSI2_SLV_REG_SRST 0x4 ++#define CSI_REG_PORT_GPREG_CSI2_PORT_CONTROL 0x8 ++ ++/* ++ * Port IRQs mapping events: ++ * IRQ0 - CSI_FE event ++ * IRQ1 - CSI_SYNC ++ * IRQ2 - S2M_SIDS0TO7 ++ * IRQ3 - S2M_SIDS8TO15 ++ */ ++#define CSI_PORT_REG_BASE_IRQ_CSI 0x80 ++#define CSI_PORT_REG_BASE_IRQ_CSI_SYNC 0xA0 ++#define CSI_PORT_REG_BASE_IRQ_S2M_SIDS0TOS7 0xC0 ++#define CSI_PORT_REG_BASE_IRQ_S2M_SIDS8TOS15 0xE0 ++ ++#define CSI_PORT_REG_BASE_IRQ_EDGE_OFFSET 0x0 ++#define CSI_PORT_REG_BASE_IRQ_MASK_OFFSET 0x4 ++#define CSI_PORT_REG_BASE_IRQ_STATUS_OFFSET 0x8 ++#define CSI_PORT_REG_BASE_IRQ_CLEAR_OFFSET 0xc ++#define CSI_PORT_REG_BASE_IRQ_ENABLE_OFFSET 0x10 ++#define CSI_PORT_REG_BASE_IRQ_LEVEL_NOT_PULSE_OFFSET 0x14 ++ ++#define IPU6SE_CSI_RX_ERROR_IRQ_MASK GENMASK(18, 0) ++#define IPU6_CSI_RX_ERROR_IRQ_MASK GENMASK(19, 0) ++ ++#define CSI_RX_NUM_ERRORS_IN_IRQ 20 ++#define CSI_RX_NUM_IRQ 32 ++ ++#define IPU_CSI_RX_IRQ_FS_VC(chn) (1 << ((chn) * 2)) ++#define IPU_CSI_RX_IRQ_FE_VC(chn) (2 << ((chn) * 2)) ++ ++/* PPI2CSI */ ++#define CSI_REG_PPI2CSI_ENABLE 0x200 ++#define CSI_REG_PPI2CSI_CONFIG_PPI_INTF 0x204 ++#define PPI_INTF_CONFIG_NOF_ENABLED_DLANES_MASK GENMASK(4, 3) ++#define CSI_REG_PPI2CSI_CONFIG_CSI_FEATURE 0x208 ++ ++enum CSI_PPI2CSI_CTRL { ++ CSI_PPI2CSI_DISABLE = 0, ++ CSI_PPI2CSI_ENABLE = 1, ++}; ++ ++/* CSI_FE */ ++#define CSI_REG_CSI_FE_ENABLE 0x280 ++#define CSI_REG_CSI_FE_MODE 0x284 ++#define CSI_REG_CSI_FE_MUX_CTRL 0x288 ++#define CSI_REG_CSI_FE_SYNC_CNTR_SEL 0x290 ++ ++enum CSI_FE_ENABLE_TYPE { ++ CSI_FE_DISABLE = 0, ++ CSI_FE_ENABLE = 1, ++}; ++ ++enum CSI_FE_MODE_TYPE { ++ CSI_FE_DPHY_MODE = 0, ++ CSI_FE_CPHY_MODE = 1, ++}; ++ ++enum CSI_FE_INPUT_SELECTOR { ++ CSI_SENSOR_INPUT = 0, ++ CSI_MIPIGEN_INPUT = 1, ++}; ++ ++enum CSI_FE_SYNC_CNTR_SEL_TYPE { ++ CSI_CNTR_SENSOR_LINE_ID = BIT(0), ++ CSI_CNTR_INT_LINE_PKT_ID = ~CSI_CNTR_SENSOR_LINE_ID, ++ CSI_CNTR_SENSOR_FRAME_ID = BIT(1), ++ CSI_CNTR_INT_FRAME_PKT_ID = ~CSI_CNTR_SENSOR_FRAME_ID, ++}; ++ ++/* CSI HUB General Purpose Registers */ ++#define CSI_REG_HUB_GPREG_SRST (CSI_REG_BASE + 0x18000) ++#define CSI_REG_HUB_GPREG_SLV_REG_SRST (CSI_REG_BASE + 0x18004) ++ ++#define CSI_REG_HUB_DRV_ACCESS_PORT(id) (CSI_REG_BASE + 0x18018 + (id) * 4) ++#define CSI_REG_HUB_FW_ACCESS_PORT_OFS 0x17000 ++#define CSI_REG_HUB_FW_ACCESS_PORT_V6OFS 0x16000 ++#define CSI_REG_HUB_FW_ACCESS_PORT(ofs, id) \ ++ (CSI_REG_BASE + (ofs) + (id) * 4) ++ ++enum CSI_PORT_CLK_GATING_SWITCH { ++ CSI_PORT_CLK_GATING_OFF = 0, ++ CSI_PORT_CLK_GATING_ON = 1, ++}; ++ ++#define CSI_REG_BASE_HUB_IRQ 0x18200 ++ ++#define IPU6_REG_ISYS_CSI_TOP_CTRL0_IRQ_EDGE 0x238200 ++#define IPU6_REG_ISYS_CSI_TOP_CTRL0_IRQ_MASK 0x238204 ++#define IPU6_REG_ISYS_CSI_TOP_CTRL0_IRQ_STATUS 0x238208 ++#define IPU6_REG_ISYS_CSI_TOP_CTRL0_IRQ_CLEAR 0x23820c ++#define IPU6_REG_ISYS_CSI_TOP_CTRL0_IRQ_ENABLE 0x238210 ++#define IPU6_REG_ISYS_CSI_TOP_CTRL0_IRQ_LEVEL_NOT_PULSE 0x238214 ++ ++#define IPU6_REG_ISYS_CSI_TOP_CTRL1_IRQ_EDGE 0x238220 ++#define IPU6_REG_ISYS_CSI_TOP_CTRL1_IRQ_MASK 0x238224 ++#define IPU6_REG_ISYS_CSI_TOP_CTRL1_IRQ_STATUS 0x238228 ++#define IPU6_REG_ISYS_CSI_TOP_CTRL1_IRQ_CLEAR 0x23822c ++#define IPU6_REG_ISYS_CSI_TOP_CTRL1_IRQ_ENABLE 0x238230 ++#define IPU6_REG_ISYS_CSI_TOP_CTRL1_IRQ_LEVEL_NOT_PULSE 0x238234 ++ ++/* MTL IPU6V6 irq ctrl0 & ctrl1 */ ++#define IPU6V6_REG_ISYS_CSI_TOP_CTRL0_IRQ_EDGE 0x238700 ++#define IPU6V6_REG_ISYS_CSI_TOP_CTRL0_IRQ_MASK 0x238704 ++#define IPU6V6_REG_ISYS_CSI_TOP_CTRL0_IRQ_STATUS 0x238708 ++#define IPU6V6_REG_ISYS_CSI_TOP_CTRL0_IRQ_CLEAR 0x23870c ++#define IPU6V6_REG_ISYS_CSI_TOP_CTRL0_IRQ_ENABLE 0x238710 ++#define IPU6V6_REG_ISYS_CSI_TOP_CTRL0_IRQ_LEVEL_NOT_PULSE 0x238714 ++ ++#define IPU6V6_REG_ISYS_CSI_TOP_CTRL1_IRQ_EDGE 0x238720 ++#define IPU6V6_REG_ISYS_CSI_TOP_CTRL1_IRQ_MASK 0x238724 ++#define IPU6V6_REG_ISYS_CSI_TOP_CTRL1_IRQ_STATUS 0x238728 ++#define IPU6V6_REG_ISYS_CSI_TOP_CTRL1_IRQ_CLEAR 0x23872c ++#define IPU6V6_REG_ISYS_CSI_TOP_CTRL1_IRQ_ENABLE 0x238730 ++#define IPU6V6_REG_ISYS_CSI_TOP_CTRL1_IRQ_LEVEL_NOT_PULSE 0x238734 ++ ++/* ++ * 3:0 CSI_PORT.irq_out[3:0] CSI_PORT_CTRL0 IRQ outputs (4bits) ++ * [0] CSI_PORT.IRQ_CTRL0_csi ++ * [1] CSI_PORT.IRQ_CTRL1_csi_sync ++ * [2] CSI_PORT.IRQ_CTRL2_s2m_sids0to7 ++ * [3] CSI_PORT.IRQ_CTRL3_s2m_sids8to15 ++ */ ++#define IPU6_ISYS_UNISPART_IRQ_CSI2(port) \ ++ (0x3 << ((port) * IPU6_CSI_IRQ_NUM_PER_PIPE)) ++ ++/* ++ * ipu6se support 2 front ends, 2 port per front end, 4 ports 0..3 ++ * sip0 - 0, 1 ++ * sip1 - 2, 3 ++ * 0 and 2 support 4 data lanes, 1 and 3 support 2 data lanes ++ * all offset are base from isys base address ++ */ ++ ++#define CSI2_HUB_GPREG_SIP_SRST(sip) (0x238038 + (sip) * 4) ++#define CSI2_HUB_GPREG_SIP_FB_PORT_CFG(sip) (0x238050 + (sip) * 4) ++ ++#define CSI2_HUB_GPREG_DPHY_TIMER_INCR 0x238040 ++#define CSI2_HUB_GPREG_HPLL_FREQ 0x238044 ++#define CSI2_HUB_GPREG_IS_CLK_RATIO 0x238048 ++#define CSI2_HUB_GPREG_HPLL_FREQ_ISCLK_RATE_OVERRIDE 0x23804c ++#define CSI2_HUB_GPREG_PORT_CLKGATING_DISABLE 0x238058 ++#define CSI2_HUB_GPREG_SIP0_CSI_RX_A_CONTROL 0x23805c ++#define CSI2_HUB_GPREG_SIP0_CSI_RX_B_CONTROL 0x238088 ++#define CSI2_HUB_GPREG_SIP1_CSI_RX_A_CONTROL 0x2380a4 ++#define CSI2_HUB_GPREG_SIP1_CSI_RX_B_CONTROL 0x2380d0 ++ ++#define CSI2_SIP_TOP_CSI_RX_BASE(sip) (0x23805c + (sip) * 0x48) ++#define CSI2_SIP_TOP_CSI_RX_PORT_BASE_0(port) (0x23805c + ((port) / 2) * 0x48) ++#define CSI2_SIP_TOP_CSI_RX_PORT_BASE_1(port) (0x238088 + ((port) / 2) * 0x48) ++ ++/* offset from port base */ ++#define CSI2_SIP_TOP_CSI_RX_PORT_CONTROL 0x0 ++#define CSI2_SIP_TOP_CSI_RX_DLY_CNT_TERMEN_CLANE 0x4 ++#define CSI2_SIP_TOP_CSI_RX_DLY_CNT_SETTLE_CLANE 0x8 ++#define CSI2_SIP_TOP_CSI_RX_DLY_CNT_TERMEN_DLANE(lane) (0xc + (lane) * 8) ++#define CSI2_SIP_TOP_CSI_RX_DLY_CNT_SETTLE_DLANE(lane) (0x10 + (lane) * 8) ++ ++#endif /* IPU6_ISYS_CSI2_REG_H */ +-- +2.43.0 + +From ba67c52b68d458e2c8ae4af08bd01a6ebe60b475 Mon Sep 17 00:00:00 2001 +From: Bingbu Cao <bingbu.cao@intel.com> +Date: Thu, 11 Jan 2024 14:55:23 +0800 +Subject: [PATCH 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 <bingbu.cao@intel.com> +Link: https://lore.kernel.org/r/20240111065531.2418836-10-bingbu.cao@intel.com +Signed-off-by: Hans de Goede <hdegoede@redhat.com> +--- + .../media/pci/intel/ipu6/ipu6-isys-dwc-phy.c | 536 +++++++++++++ + .../media/pci/intel/ipu6/ipu6-isys-jsl-phy.c | 242 ++++++ + .../media/pci/intel/ipu6/ipu6-isys-mcd-phy.c | 720 ++++++++++++++++++ + 3 files changed, 1498 insertions(+) + create mode 100644 drivers/media/pci/intel/ipu6/ipu6-isys-dwc-phy.c + create mode 100644 drivers/media/pci/intel/ipu6/ipu6-isys-jsl-phy.c + create mode 100644 drivers/media/pci/intel/ipu6/ipu6-isys-mcd-phy.c + +diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys-dwc-phy.c b/drivers/media/pci/intel/ipu6/ipu6-isys-dwc-phy.c +new file mode 100644 +index 000000000000..a4bb5ff51d4e +--- /dev/null ++++ b/drivers/media/pci/intel/ipu6/ipu6-isys-dwc-phy.c +@@ -0,0 +1,536 @@ ++// SPDX-License-Identifier: GPL-2.0-only ++/* ++ * Copyright (C) 2013 - 2023 Intel Corporation ++ */ ++ ++#include <linux/bitfield.h> ++#include <linux/bits.h> ++#include <linux/delay.h> ++#include <linux/device.h> ++#include <linux/iopoll.h> ++#include <linux/math64.h> ++ ++#include "ipu6-bus.h" ++#include "ipu6-isys.h" ++#include "ipu6-platform-isys-csi2-reg.h" ++ ++#define IPU6_DWC_DPHY_BASE(i) (0x238038 + 0x34 * (i)) ++#define IPU6_DWC_DPHY_RSTZ 0x00 ++#define IPU6_DWC_DPHY_SHUTDOWNZ 0x04 ++#define IPU6_DWC_DPHY_HSFREQRANGE 0x08 ++#define IPU6_DWC_DPHY_CFGCLKFREQRANGE 0x0c ++#define IPU6_DWC_DPHY_TEST_IFC_ACCESS_MODE 0x10 ++#define IPU6_DWC_DPHY_TEST_IFC_REQ 0x14 ++#define IPU6_DWC_DPHY_TEST_IFC_REQ_COMPLETION 0x18 ++#define IPU6_DWC_DPHY_DFT_CTRL0 0x28 ++#define IPU6_DWC_DPHY_DFT_CTRL1 0x2c ++#define IPU6_DWC_DPHY_DFT_CTRL2 0x30 ++ ++/* ++ * test IFC request definition: ++ * - req: 0 for read, 1 for write ++ * - 12 bits address ++ * - 8bits data (will ignore for read) ++ * --24----16------4-----0 ++ * --|-data-|-addr-|-req-| ++ */ ++#define IFC_REQ(req, addr, data) (FIELD_PREP(GENMASK(23, 16), data) | \ ++ FIELD_PREP(GENMASK(15, 4), addr) | \ ++ FIELD_PREP(GENMASK(1, 0), req)) ++ ++#define TEST_IFC_REQ_READ 0 ++#define TEST_IFC_REQ_WRITE 1 ++#define TEST_IFC_REQ_RESET 2 ++ ++#define TEST_IFC_ACCESS_MODE_FSM 0 ++#define TEST_IFC_ACCESS_MODE_IFC_CTL 1 ++ ++enum phy_fsm_state { ++ PHY_FSM_STATE_POWERON = 0, ++ PHY_FSM_STATE_BGPON = 1, ++ PHY_FSM_STATE_CAL_TYPE = 2, ++ PHY_FSM_STATE_BURNIN_CAL = 3, ++ PHY_FSM_STATE_TERMCAL = 4, ++ PHY_FSM_STATE_OFFSETCAL = 5, ++ PHY_FSM_STATE_OFFSET_LANE = 6, ++ PHY_FSM_STATE_IDLE = 7, ++ PHY_FSM_STATE_ULP = 8, ++ PHY_FSM_STATE_DDLTUNNING = 9, ++ PHY_FSM_STATE_SKEW_BACKWARD = 10, ++ PHY_FSM_STATE_INVALID, ++}; ++ ++static void dwc_dphy_write(struct ipu6_isys *isys, u32 phy_id, u32 addr, ++ u32 data) ++{ ++ struct device *dev = &isys->adev->auxdev.dev; ++ void __iomem *isys_base = isys->pdata->base; ++ void __iomem *base = isys_base + IPU6_DWC_DPHY_BASE(phy_id); ++ ++ dev_dbg(dev, "write: reg 0x%lx = data 0x%x", base + addr - isys_base, ++ data); ++ writel(data, base + addr); ++} ++ ++static u32 dwc_dphy_read(struct ipu6_isys *isys, u32 phy_id, u32 addr) ++{ ++ struct device *dev = &isys->adev->auxdev.dev; ++ void __iomem *isys_base = isys->pdata->base; ++ void __iomem *base = isys_base + IPU6_DWC_DPHY_BASE(phy_id); ++ u32 data; ++ ++ data = readl(base + addr); ++ dev_dbg(dev, "read: reg 0x%lx = data 0x%x", base + addr - isys_base, ++ data); ++ ++ return data; ++} ++ ++static void dwc_dphy_write_mask(struct ipu6_isys *isys, u32 phy_id, u32 addr, ++ u32 data, u8 shift, u8 width) ++{ ++ u32 temp; ++ u32 mask; ++ ++ mask = (1 << width) - 1; ++ temp = dwc_dphy_read(isys, phy_id, addr); ++ temp &= ~(mask << shift); ++ temp |= (data & mask) << shift; ++ dwc_dphy_write(isys, phy_id, addr, temp); ++} ++ ++static u32 __maybe_unused dwc_dphy_read_mask(struct ipu6_isys *isys, u32 phy_id, ++ u32 addr, u8 shift, u8 width) ++{ ++ u32 val; ++ ++ val = dwc_dphy_read(isys, phy_id, addr) >> shift; ++ return val & ((1 << width) - 1); ++} ++ ++#define DWC_DPHY_TIMEOUT (5 * USEC_PER_SEC) ++static int dwc_dphy_ifc_read(struct ipu6_isys *isys, u32 phy_id, u32 addr, ++ u32 *val) ++{ ++ struct device *dev = &isys->adev->auxdev.dev; ++ void __iomem *isys_base = isys->pdata->base; ++ void __iomem *base = isys_base + IPU6_DWC_DPHY_BASE(phy_id); ++ void __iomem *reg; ++ u32 completion; ++ int ret; ++ ++ dwc_dphy_write(isys, phy_id, IPU6_DWC_DPHY_TEST_IFC_REQ, ++ IFC_REQ(TEST_IFC_REQ_READ, addr, 0)); ++ reg = base + IPU6_DWC_DPHY_TEST_IFC_REQ_COMPLETION; ++ ret = readl_poll_timeout(reg, completion, !(completion & BIT(0)), ++ 10, DWC_DPHY_TIMEOUT); ++ if (ret) { ++ dev_err(dev, "DWC ifc request read timeout\n"); ++ return ret; ++ } ++ ++ *val = completion >> 8 & 0xff; ++ *val = FIELD_GET(GENMASK(15, 8), completion); ++ dev_dbg(dev, "DWC ifc read 0x%x = 0x%x", addr, *val); ++ ++ return 0; ++} ++ ++static int dwc_dphy_ifc_write(struct ipu6_isys *isys, u32 phy_id, u32 addr, ++ u32 data) ++{ ++ struct device *dev = &isys->adev->auxdev.dev; ++ void __iomem *isys_base = isys->pdata->base; ++ void __iomem *base = isys_base + IPU6_DWC_DPHY_BASE(phy_id); ++ void __iomem *reg; ++ u32 completion; ++ int ret; ++ ++ dwc_dphy_write(isys, phy_id, IPU6_DWC_DPHY_TEST_IFC_REQ, ++ IFC_REQ(TEST_IFC_REQ_WRITE, addr, data)); ++ completion = readl(base + IPU6_DWC_DPHY_TEST_IFC_REQ_COMPLETION); ++ reg = base + IPU6_DWC_DPHY_TEST_IFC_REQ_COMPLETION; ++ ret = readl_poll_timeout(reg, completion, !(completion & BIT(0)), ++ 10, DWC_DPHY_TIMEOUT); ++ if (ret) ++ dev_err(dev, "DWC ifc request write timeout\n"); ++ ++ return ret; ++} ++ ++static void dwc_dphy_ifc_write_mask(struct ipu6_isys *isys, u32 phy_id, ++ u32 addr, u32 data, u8 shift, u8 width) ++{ ++ u32 temp, mask; ++ int ret; ++ ++ ret = dwc_dphy_ifc_read(isys, phy_id, addr, &temp); ++ if (ret) ++ return; ++ ++ mask = (1 << width) - 1; ++ temp &= ~(mask << shift); ++ temp |= (data & mask) << shift; ++ dwc_dphy_ifc_write(isys, phy_id, addr, temp); ++} ++ ++static u32 dwc_dphy_ifc_read_mask(struct ipu6_isys *isys, u32 phy_id, u32 addr, ++ u8 shift, u8 width) ++{ ++ int ret; ++ u32 val; ++ ++ ret = dwc_dphy_ifc_read(isys, phy_id, addr, &val); ++ if (ret) ++ return 0; ++ ++ return ((val >> shift) & ((1 << width) - 1)); ++} ++ ++static int dwc_dphy_pwr_up(struct ipu6_isys *isys, u32 phy_id) ++{ ++ struct device *dev = &isys->adev->auxdev.dev; ++ u32 fsm_state; ++ int ret; ++ ++ dwc_dphy_write(isys, phy_id, IPU6_DWC_DPHY_RSTZ, 1); ++ usleep_range(10, 20); ++ dwc_dphy_write(isys, phy_id, IPU6_DWC_DPHY_SHUTDOWNZ, 1); ++ ++ ret = read_poll_timeout(dwc_dphy_ifc_read_mask, fsm_state, ++ (fsm_state == PHY_FSM_STATE_IDLE || ++ fsm_state == PHY_FSM_STATE_ULP), ++ 100, DWC_DPHY_TIMEOUT, false, isys, ++ phy_id, 0x1e, 0, 4); ++ ++ if (ret) ++ dev_err(dev, "Dphy %d power up failed, state 0x%x", phy_id, ++ fsm_state); ++ ++ return ret; ++} ++ ++struct dwc_dphy_freq_range { ++ u8 hsfreq; ++ u16 min; ++ u16 max; ++ u16 default_mbps; ++ u16 osc_freq_target; ++}; ++ ++#define DPHY_FREQ_RANGE_NUM (63) ++#define DPHY_FREQ_RANGE_INVALID_INDEX (0xff) ++static const struct dwc_dphy_freq_range freqranges[DPHY_FREQ_RANGE_NUM] = { ++ {0x00, 80, 97, 80, 335}, ++ {0x10, 80, 107, 90, 335}, ++ {0x20, 84, 118, 100, 335}, ++ {0x30, 93, 128, 110, 335}, ++ {0x01, 103, 139, 120, 335}, ++ {0x11, 112, 149, 130, 335}, ++ {0x21, 122, 160, 140, 335}, ++ {0x31, 131, 170, 150, 335}, ++ {0x02, 141, 181, 160, 335}, ++ {0x12, 150, 191, 170, 335}, ++ {0x22, 160, 202, 180, 335}, ++ {0x32, 169, 212, 190, 335}, ++ {0x03, 183, 228, 205, 335}, ++ {0x13, 198, 244, 220, 335}, ++ {0x23, 212, 259, 235, 335}, ++ {0x33, 226, 275, 250, 335}, ++ {0x04, 250, 301, 275, 335}, ++ {0x14, 274, 328, 300, 335}, ++ {0x25, 297, 354, 325, 335}, ++ {0x35, 321, 380, 350, 335}, ++ {0x05, 369, 433, 400, 335}, ++ {0x16, 416, 485, 450, 335}, ++ {0x26, 464, 538, 500, 335}, ++ {0x37, 511, 590, 550, 335}, ++ {0x07, 559, 643, 600, 335}, ++ {0x18, 606, 695, 650, 335}, ++ {0x28, 654, 748, 700, 335}, ++ {0x39, 701, 800, 750, 335}, ++ {0x09, 749, 853, 800, 335}, ++ {0x19, 796, 905, 850, 335}, ++ {0x29, 844, 958, 900, 335}, ++ {0x3a, 891, 1010, 950, 335}, ++ {0x0a, 939, 1063, 1000, 335}, ++ {0x1a, 986, 1115, 1050, 335}, ++ {0x2a, 1034, 1168, 1100, 335}, ++ {0x3b, 1081, 1220, 1150, 335}, ++ {0x0b, 1129, 1273, 1200, 335}, ++ {0x1b, 1176, 1325, 1250, 335}, ++ {0x2b, 1224, 1378, 1300, 335}, ++ {0x3c, 1271, 1430, 1350, 335}, ++ {0x0c, 1319, 1483, 1400, 335}, ++ {0x1c, 1366, 1535, 1450, 335}, ++ {0x2c, 1414, 1588, 1500, 335}, ++ {0x3d, 1461, 1640, 1550, 208}, ++ {0x0d, 1509, 1693, 1600, 214}, ++ {0x1d, 1556, 1745, 1650, 221}, ++ {0x2e, 1604, 1798, 1700, 228}, ++ {0x3e, 1651, 1850, 1750, 234}, ++ {0x0e, 1699, 1903, 1800, 241}, ++ {0x1e, 1746, 1955, 1850, 248}, ++ {0x2f, 1794, 2008, 1900, 255}, ++ {0x3f, 1841, 2060, 1950, 261}, ++ {0x0f, 1889, 2113, 2000, 268}, ++ {0x40, 1936, 2165, 2050, 275}, ++ {0x41, 1984, 2218, 2100, 281}, ++ {0x42, 2031, 2270, 2150, 288}, ++ {0x43, 2079, 2323, 2200, 294}, ++ {0x44, 2126, 2375, 2250, 302}, ++ {0x45, 2174, 2428, 2300, 308}, ++ {0x46, 2221, 2480, 2350, 315}, ++ {0x47, 2269, 2500, 2400, 321}, ++ {0x48, 2316, 2500, 2450, 328}, ++ {0x49, 2364, 2500, 2500, 335} ++}; ++ ++static u16 get_hsfreq_by_mbps(u32 mbps) ++{ ++ unsigned int i = DPHY_FREQ_RANGE_NUM; ++ ++ while (i--) { ++ if (freqranges[i].default_mbps == mbps || ++ (mbps >= freqranges[i].min && mbps <= freqranges[i].max)) ++ return i; ++ } ++ ++ return DPHY_FREQ_RANGE_INVALID_INDEX; ++} ++ ++static int ipu6_isys_dwc_phy_config(struct ipu6_isys *isys, ++ u32 phy_id, u32 mbps) ++{ ++ struct ipu6_bus_device *adev = isys->adev; ++ struct device *dev = &adev->auxdev.dev; ++ struct ipu6_device *isp = adev->isp; ++ u32 cfg_clk_freqrange; ++ u32 osc_freq_target; ++ u32 index; ++ ++ dev_dbg(dev, "config Dphy %u with %u mbps", phy_id, mbps); ++ ++ index = get_hsfreq_by_mbps(mbps); ++ if (index == DPHY_FREQ_RANGE_INVALID_INDEX) { ++ dev_err(dev, "link freq not found for mbps %u", mbps); ++ return -EINVAL; ++ } ++ ++ dwc_dphy_write_mask(isys, phy_id, IPU6_DWC_DPHY_HSFREQRANGE, ++ freqranges[index].hsfreq, 0, 7); ++ ++ /* Force termination Calibration */ ++ if (isys->phy_termcal_val) { ++ dwc_dphy_ifc_write_mask(isys, phy_id, 0x20a, 0x1, 0, 1); ++ dwc_dphy_ifc_write_mask(isys, phy_id, 0x209, 0x3, 0, 2); ++ dwc_dphy_ifc_write_mask(isys, phy_id, 0x209, ++ isys->phy_termcal_val, 4, 4); ++ } ++ ++ /* ++ * Enable override to configure the DDL target oscillation ++ * frequency on bit 0 of register 0xe4 ++ */ ++ dwc_dphy_ifc_write_mask(isys, phy_id, 0xe4, 0x1, 0, 1); ++ /* ++ * configure registers 0xe2, 0xe3 with the ++ * appropriate DDL target oscillation frequency ++ * 0x1cc(460) ++ */ ++ osc_freq_target = freqranges[index].osc_freq_target; ++ dwc_dphy_ifc_write_mask(isys, phy_id, 0xe2, ++ osc_freq_target & 0xff, 0, 8); ++ dwc_dphy_ifc_write_mask(isys, phy_id, 0xe3, ++ (osc_freq_target >> 8) & 0xf, 0, 4); ++ ++ if (mbps < 1500) { ++ /* deskew_polarity_rw, for < 1.5Gbps */ ++ dwc_dphy_ifc_write_mask(isys, phy_id, 0x8, 0x1, 5, 1); ++ } ++ ++ /* ++ * Set cfgclkfreqrange[5:0] = round[(Fcfg_clk(MHz)-17)*4] ++ * (38.4 - 17) * 4 = ~85 (0x55) ++ */ ++ cfg_clk_freqrange = (isp->buttress.ref_clk - 170) * 4 / 10; ++ dev_dbg(dev, "ref_clk = %u clk_freqrange = %u", ++ isp->buttress.ref_clk, cfg_clk_freqrange); ++ dwc_dphy_write_mask(isys, phy_id, IPU6_DWC_DPHY_CFGCLKFREQRANGE, ++ cfg_clk_freqrange, 0, 8); ++ ++ dwc_dphy_write_mask(isys, phy_id, IPU6_DWC_DPHY_DFT_CTRL2, 0x1, 4, 1); ++ dwc_dphy_write_mask(isys, phy_id, IPU6_DWC_DPHY_DFT_CTRL2, 0x1, 8, 1); ++ ++ return 0; ++} ++ ++static void ipu6_isys_dwc_phy_aggr_setup(struct ipu6_isys *isys, u32 master, ++ u32 slave, u32 mbps) ++{ ++ /* Config mastermacro */ ++ dwc_dphy_ifc_write_mask(isys, master, 0x133, 0x1, 0, 1); ++ dwc_dphy_ifc_write_mask(isys, slave, 0x133, 0x0, 0, 1); ++ ++ /* Config master PHY clk lane to drive long channel clk */ ++ dwc_dphy_ifc_write_mask(isys, master, 0x307, 0x1, 2, 1); ++ dwc_dphy_ifc_write_mask(isys, slave, 0x307, 0x0, 2, 1); ++ ++ /* Config both PHYs data lanes to get clk from long channel */ ++ dwc_dphy_ifc_write_mask(isys, master, 0x508, 0x1, 5, 1); ++ dwc_dphy_ifc_write_mask(isys, slave, 0x508, 0x1, 5, 1); ++ dwc_dphy_ifc_write_mask(isys, master, 0x708, 0x1, 5, 1); ++ dwc_dphy_ifc_write_mask(isys, slave, 0x708, 0x1, 5, 1); ++ ++ /* Config slave PHY clk lane to bypass long channel clk to DDR clk */ ++ dwc_dphy_ifc_write_mask(isys, master, 0x308, 0x0, 3, 1); ++ dwc_dphy_ifc_write_mask(isys, slave, 0x308, 0x1, 3, 1); ++ ++ /* Override slave PHY clk lane enable (DPHYRXCLK_CLL_demux module) */ ++ dwc_dphy_ifc_write_mask(isys, slave, 0xe0, 0x3, 0, 2); ++ ++ /* Override slave PHY DDR clk lane enable (DPHYHSRX_div124 module) */ ++ dwc_dphy_ifc_write_mask(isys, slave, 0xe1, 0x1, 1, 1); ++ dwc_dphy_ifc_write_mask(isys, slave, 0x307, 0x1, 3, 1); ++ ++ /* Turn off slave PHY LP-RX clk lane */ ++ dwc_dphy_ifc_write_mask(isys, slave, 0x304, 0x1, 7, 1); ++ dwc_dphy_ifc_write_mask(isys, slave, 0x305, 0xa, 0, 5); ++} ++ ++#define PHY_E 4 ++static int ipu6_isys_dwc_phy_powerup_ack(struct ipu6_isys *isys, u32 phy_id) ++{ ++ struct device *dev = &isys->adev->auxdev.dev; ++ u32 rescal_done; ++ int ret; ++ ++ ret = dwc_dphy_pwr_up(isys, phy_id); ++ if (ret != 0) { ++ dev_err(dev, "Dphy %u power up failed(%d)", phy_id, ret); ++ return ret; ++ } ++ ++ /* reset forcerxmode */ ++ dwc_dphy_write_mask(isys, phy_id, IPU6_DWC_DPHY_DFT_CTRL2, 0, 4, 1); ++ dwc_dphy_write_mask(isys, phy_id, IPU6_DWC_DPHY_DFT_CTRL2, 0, 8, 1); ++ ++ dev_dbg(dev, "Dphy %u is ready!", phy_id); ++ ++ if (phy_id != PHY_E || isys->phy_termcal_val) ++ return 0; ++ ++ usleep_range(100, 200); ++ rescal_done = dwc_dphy_ifc_read_mask(isys, phy_id, 0x221, 7, 1); ++ if (rescal_done) { ++ isys->phy_termcal_val = dwc_dphy_ifc_read_mask(isys, phy_id, ++ 0x220, 2, 4); ++ dev_dbg(dev, "termcal done with value = %u", ++ isys->phy_termcal_val); ++ } ++ ++ return 0; ++} ++ ++static void ipu6_isys_dwc_phy_reset(struct ipu6_isys *isys, u32 phy_id) ++{ ++ dev_dbg(&isys->adev->auxdev.dev, "Reset phy %u", phy_id); ++ ++ dwc_dphy_write(isys, phy_id, IPU6_DWC_DPHY_SHUTDOWNZ, 0); ++ dwc_dphy_write(isys, phy_id, IPU6_DWC_DPHY_RSTZ, 0); ++ dwc_dphy_write(isys, phy_id, IPU6_DWC_DPHY_TEST_IFC_ACCESS_MODE, ++ TEST_IFC_ACCESS_MODE_FSM); ++ dwc_dphy_write(isys, phy_id, IPU6_DWC_DPHY_TEST_IFC_REQ, ++ TEST_IFC_REQ_RESET); ++} ++ ++int ipu6_isys_dwc_phy_set_power(struct ipu6_isys *isys, ++ struct ipu6_isys_csi2_config *cfg, ++ const struct ipu6_isys_csi2_timing *timing, ++ bool on) ++{ ++ struct device *dev = &isys->adev->auxdev.dev; ++ void __iomem *isys_base = isys->pdata->base; ++ u32 phy_id, primary, secondary; ++ u32 nlanes, port, mbps; ++ s64 link_freq; ++ int ret; ++ ++ port = cfg->port; ++ ++ if (!isys_base || port >= isys->pdata->ipdata->csi2.nports) { ++ dev_warn(dev, "invalid port ID %d\n", port); ++ return -EINVAL; ++ } ++ ++ nlanes = cfg->nlanes; ++ /* only port 0, 2 and 4 support 4 lanes */ ++ if (nlanes == 4 && port % 2) { ++ dev_err(dev, "invalid csi-port %u with %u lanes\n", port, ++ nlanes); ++ return -EINVAL; ++ } ++ ++ link_freq = ipu6_isys_csi2_get_link_freq(&isys->csi2[port]); ++ if (link_freq < 0) { ++ dev_err(dev, "get link freq failed(%lld).\n", link_freq); ++ return link_freq; ++ } ++ ++ mbps = div_u64(link_freq, 500000); ++ ++ phy_id = port; ++ primary = port & ~1; ++ secondary = primary + 1; ++ if (on) { ++ if (nlanes == 4) { ++ dev_dbg(dev, "config phy %u and %u in aggr mode\n", ++ primary, secondary); ++ ++ ipu6_isys_dwc_phy_reset(isys, primary); ++ ipu6_isys_dwc_phy_reset(isys, secondary); ++ ipu6_isys_dwc_phy_aggr_setup(isys, primary, ++ secondary, mbps); ++ ++ ret = ipu6_isys_dwc_phy_config(isys, primary, mbps); ++ if (ret) ++ return ret; ++ ret = ipu6_isys_dwc_phy_config(isys, secondary, mbps); ++ if (ret) ++ return ret; ++ ++ ret = ipu6_isys_dwc_phy_powerup_ack(isys, primary); ++ if (ret) ++ return ret; ++ ++ ret = ipu6_isys_dwc_phy_powerup_ack(isys, secondary); ++ return ret; ++ } ++ ++ dev_dbg(dev, "config phy %u with %u lanes in non-aggr mode\n", ++ phy_id, nlanes); ++ ++ ipu6_isys_dwc_phy_reset(isys, phy_id); ++ ret = ipu6_isys_dwc_phy_config(isys, phy_id, mbps); ++ if (ret) ++ return ret; ++ ++ ret = ipu6_isys_dwc_phy_powerup_ack(isys, phy_id); ++ return ret; ++ } ++ ++ if (nlanes == 4) { ++ dev_dbg(dev, "Power down phy %u and phy %u for port %u\n", ++ primary, secondary, port); ++ ipu6_isys_dwc_phy_reset(isys, secondary); ++ ipu6_isys_dwc_phy_reset(isys, primary); ++ ++ return 0; ++ } ++ ++ dev_dbg(dev, "Powerdown phy %u with %u lanes\n", phy_id, nlanes); ++ ++ ipu6_isys_dwc_phy_reset(isys, phy_id); ++ ++ return 0; ++} +diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys-jsl-phy.c b/drivers/media/pci/intel/ipu6/ipu6-isys-jsl-phy.c +new file mode 100644 +index 000000000000..dcc7743e0cee +--- /dev/null ++++ b/drivers/media/pci/intel/ipu6/ipu6-isys-jsl-phy.c +@@ -0,0 +1,242 @@ ++// SPDX-License-Identifier: GPL-2.0-only ++/* ++ * Copyright (C) 2013 - 2023 Intel Corporation ++ */ ++ ++#include <linux/bitfield.h> ++#include <linux/bits.h> ++#include <linux/device.h> ++#include <linux/io.h> ++ ++#include "ipu6-bus.h" ++#include "ipu6-isys.h" ++#include "ipu6-isys-csi2.h" ++#include "ipu6-platform-isys-csi2-reg.h" ++ ++/* only use BB0, BB2, BB4, and BB6 on PHY0 */ ++#define IPU6SE_ISYS_PHY_BB_NUM 4 ++#define IPU6SE_ISYS_PHY_0_BASE 0x10000 ++ ++#define PHY_CPHY_DLL_OVRD(x) (0x100 + 0x100 * (x)) ++#define PHY_CPHY_RX_CONTROL1(x) (0x110 + 0x100 * (x)) ++#define PHY_DPHY_CFG(x) (0x148 + 0x100 * (x)) ++#define PHY_BB_AFE_CONFIG(x) (0x174 + 0x100 * (x)) ++ ++/* ++ * use port_cfg to configure that which data lanes used ++ * +---------+ +------+ +-----+ ++ * | port0 x4<-----| | | | ++ * | | | port | | | ++ * | port1 x2<-----| | | | ++ * | | | <-| PHY | ++ * | port2 x4<-----| | | | ++ * | | |config| | | ++ * | port3 x2<-----| | | | ++ * +---------+ +------+ +-----+ ++ */ ++static const unsigned int csi2_port_cfg[][3] = { ++ {0, 0, 0x1f}, /* no link */ ++ {4, 0, 0x10}, /* x4 + x4 config */ ++ {2, 0, 0x12}, /* x2 + x2 config */ ++ {1, 0, 0x13}, /* x1 + x1 config */ ++ {2, 1, 0x15}, /* x2x1 + x2x1 config */ ++ {1, 1, 0x16}, /* x1x1 + x1x1 config */ ++ {2, 2, 0x18}, /* x2x2 + x2x2 config */ ++ {1, 2, 0x19} /* x1x2 + x1x2 config */ ++}; ++ ++/* port, nlanes, bbindex, portcfg */ ++static const unsigned int phy_port_cfg[][4] = { ++ /* sip0 */ ++ {0, 1, 0, 0x15}, ++ {0, 2, 0, 0x15}, ++ {0, 4, 0, 0x15}, ++ {0, 4, 2, 0x22}, ++ /* sip1 */ ++ {2, 1, 4, 0x15}, ++ {2, 2, 4, 0x15}, ++ {2, 4, 4, 0x15}, ++ {2, 4, 6, 0x22} ++}; ++ ++static void ipu6_isys_csi2_phy_config_by_port(struct ipu6_isys *isys, ++ unsigned int port, ++ unsigned int nlanes) ++{ ++ struct device *dev = &isys->adev->auxdev.dev; ++ void __iomem *base = isys->adev->isp->base; ++ unsigned int bbnum; ++ u32 val, reg, i; ++ ++ dev_dbg(dev, "port %u with %u lanes", port, nlanes); ++ ++ /* only support <1.5Gbps */ ++ for (i = 0; i < IPU6SE_ISYS_PHY_BB_NUM; i++) { ++ /* cphy_dll_ovrd.crcdc_fsm_dlane0 = 13 */ ++ reg = IPU6SE_ISYS_PHY_0_BASE + PHY_CPHY_DLL_OVRD(i); ++ val = readl(base + reg); ++ val |= FIELD_PREP(GENMASK(6, 1), 13); ++ writel(val, base + reg); ++ ++ /* cphy_rx_control1.en_crc1 = 1 */ ++ reg = IPU6SE_ISYS_PHY_0_BASE + PHY_CPHY_RX_CONTROL1(i); ++ val = readl(base + reg); ++ val |= BIT(31); ++ writel(val, base + reg); ++ ++ /* dphy_cfg.reserved = 1, .lden_from_dll_ovrd_0 = 1 */ ++ reg = IPU6SE_ISYS_PHY_0_BASE + PHY_DPHY_CFG(i); ++ val = readl(base + reg); ++ val |= BIT(25) | BIT(26); ++ writel(val, base + reg); ++ ++ /* cphy_dll_ovrd.lden_crcdc_fsm_dlane0 = 1 */ ++ reg = IPU6SE_ISYS_PHY_0_BASE + PHY_CPHY_DLL_OVRD(i); ++ val = readl(base + reg); ++ val |= BIT(0); ++ writel(val, base + reg); ++ } ++ ++ /* Front end config, use minimal channel loss */ ++ for (i = 0; i < ARRAY_SIZE(phy_port_cfg); i++) { ++ if (phy_port_cfg[i][0] == port && ++ phy_port_cfg[i][1] == nlanes) { ++ bbnum = phy_port_cfg[i][2] / 2; ++ reg = IPU6SE_ISYS_PHY_0_BASE + PHY_BB_AFE_CONFIG(bbnum); ++ val = readl(base + reg); ++ val |= phy_port_cfg[i][3]; ++ writel(val, base + reg); ++ } ++ } ++} ++ ++static void ipu6_isys_csi2_rx_control(struct ipu6_isys *isys) ++{ ++ void __iomem *base = isys->adev->isp->base; ++ u32 val, reg; ++ ++ reg = CSI2_HUB_GPREG_SIP0_CSI_RX_A_CONTROL; ++ val = readl(base + reg); ++ val |= BIT(0); ++ writel(val, base + CSI2_HUB_GPREG_SIP0_CSI_RX_A_CONTROL); ++ ++ reg = CSI2_HUB_GPREG_SIP0_CSI_RX_B_CONTROL; ++ val = readl(base + reg); ++ val |= BIT(0); ++ writel(val, base + CSI2_HUB_GPREG_SIP0_CSI_RX_B_CONTROL); ++ ++ reg = CSI2_HUB_GPREG_SIP1_CSI_RX_A_CONTROL; ++ val = readl(base + reg); ++ val |= BIT(0); ++ writel(val, base + CSI2_HUB_GPREG_SIP1_CSI_RX_A_CONTROL); ++ ++ reg = CSI2_HUB_GPREG_SIP1_CSI_RX_B_CONTROL; ++ val = readl(base + reg); ++ val |= BIT(0); ++ writel(val, base + CSI2_HUB_GPREG_SIP1_CSI_RX_B_CONTROL); ++} ++ ++static int ipu6_isys_csi2_set_port_cfg(struct ipu6_isys *isys, ++ unsigned int port, unsigned int nlanes) ++{ ++ struct device *dev = &isys->adev->auxdev.dev; ++ unsigned int sip = port / 2; ++ unsigned int index; ++ ++ switch (nlanes) { ++ case 1: ++ index = 5; ++ break; ++ case 2: ++ index = 6; ++ break; ++ case 4: ++ index = 1; ++ break; ++ default: ++ dev_err(dev, "lanes nr %u is unsupported\n", nlanes); ++ return -EINVAL; ++ } ++ ++ dev_dbg(dev, "port config for port %u with %u lanes\n", port, nlanes); ++ ++ writel(csi2_port_cfg[index][2], ++ isys->pdata->base + CSI2_HUB_GPREG_SIP_FB_PORT_CFG(sip)); ++ ++ return 0; ++} ++ ++static void ++ipu6_isys_csi2_set_timing(struct ipu6_isys *isys, ++ const struct ipu6_isys_csi2_timing *timing, ++ unsigned int port, unsigned int nlanes) ++{ ++ struct device *dev = &isys->adev->auxdev.dev; ++ void __iomem *reg; ++ u32 port_base; ++ u32 i; ++ ++ port_base = (port % 2) ? CSI2_SIP_TOP_CSI_RX_PORT_BASE_1(port) : ++ CSI2_SIP_TOP_CSI_RX_PORT_BASE_0(port); ++ ++ dev_dbg(dev, "set timing for port %u with %u lanes\n", port, nlanes); ++ ++ reg = isys->pdata->base + port_base; ++ reg += CSI2_SIP_TOP_CSI_RX_DLY_CNT_TERMEN_CLANE; ++ ++ writel(timing->ctermen, reg); ++ ++ reg = isys->pdata->base + port_base; ++ reg += CSI2_SIP_TOP_CSI_RX_DLY_CNT_SETTLE_CLANE; ++ writel(timing->csettle, reg); ++ ++ for (i = 0; i < nlanes; i++) { ++ reg = isys->pdata->base + port_base; ++ reg += CSI2_SIP_TOP_CSI_RX_DLY_CNT_TERMEN_DLANE(i); ++ writel(timing->dtermen, reg); ++ ++ reg = isys->pdata->base + port_base; ++ reg += CSI2_SIP_TOP_CSI_RX_DLY_CNT_SETTLE_DLANE(i); ++ writel(timing->dsettle, reg); ++ } ++} ++ ++#define DPHY_TIMER_INCR 0x28 ++int ipu6_isys_jsl_phy_set_power(struct ipu6_isys *isys, ++ struct ipu6_isys_csi2_config *cfg, ++ const struct ipu6_isys_csi2_timing *timing, ++ bool on) ++{ ++ struct device *dev = &isys->adev->auxdev.dev; ++ void __iomem *isys_base = isys->pdata->base; ++ int ret = 0; ++ u32 nlanes; ++ u32 port; ++ ++ if (!on) ++ return 0; ++ ++ port = cfg->port; ++ nlanes = cfg->nlanes; ++ ++ if (!isys_base || port >= isys->pdata->ipdata->csi2.nports) { ++ dev_warn(dev, "invalid port ID %d\n", port); ++ return -EINVAL; ++ } ++ ++ ipu6_isys_csi2_phy_config_by_port(isys, port, nlanes); ++ ++ writel(DPHY_TIMER_INCR, ++ isys->pdata->base + CSI2_HUB_GPREG_DPHY_TIMER_INCR); ++ ++ /* set port cfg and rx timing */ ++ ipu6_isys_csi2_set_timing(isys, timing, port, nlanes); ++ ++ ret = ipu6_isys_csi2_set_port_cfg(isys, port, nlanes); ++ if (ret) ++ return ret; ++ ++ ipu6_isys_csi2_rx_control(isys); ++ ++ return 0; ++} +diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys-mcd-phy.c b/drivers/media/pci/intel/ipu6/ipu6-isys-mcd-phy.c +new file mode 100644 +index 000000000000..9abf389a05f1 +--- /dev/null ++++ b/drivers/media/pci/intel/ipu6/ipu6-isys-mcd-phy.c +@@ -0,0 +1,720 @@ ++// SPDX-License-Identifier: GPL-2.0-only ++/* ++ * Copyright (C) 2013 - 2023 Intel Corporation ++ */ ++ ++#include <linux/bits.h> ++#include <linux/container_of.h> ++#include <linux/device.h> ++#include <linux/iopoll.h> ++#include <linux/list.h> ++#include <linux/refcount.h> ++#include <linux/time64.h> ++ ++#include <media/v4l2-async.h> ++ ++#include "ipu6.h" ++#include "ipu6-bus.h" ++#include "ipu6-isys.h" ++#include "ipu6-isys-csi2.h" ++#include "ipu6-platform-isys-csi2-reg.h" ++ ++#define CSI_REG_HUB_GPREG_PHY_CTL(id) (CSI_REG_BASE + 0x18008 + (id) * 0x8) ++#define CSI_REG_HUB_GPREG_PHY_CTL_RESET BIT(4) ++#define CSI_REG_HUB_GPREG_PHY_CTL_PWR_EN BIT(0) ++#define CSI_REG_HUB_GPREG_PHY_STATUS(id) (CSI_REG_BASE + 0x1800c + (id) * 0x8) ++#define CSI_REG_HUB_GPREG_PHY_POWER_ACK BIT(0) ++#define CSI_REG_HUB_GPREG_PHY_READY BIT(4) ++ ++#define MCD_PHY_POWER_STATUS_TIMEOUT (200 * USEC_PER_MSEC) ++ ++/* ++ * bridge to phy in buttress reg map, each phy has 16 kbytes ++ * only 2 phys for TGL U and Y ++ */ ++#define IPU6_ISYS_MCD_PHY_BASE(i) (0x10000 + (i) * 0x4000) ++ ++/* ++ * There are 2 MCD DPHY instances on TGL and 1 MCD DPHY instance on ADL. ++ * Each MCD PHY has 12-lanes which has 8 data lanes and 4 clock lanes. ++ * CSI port 1, 3 (5, 7) can support max 2 data lanes. ++ * CSI port 0, 2 (4, 6) can support max 4 data lanes. ++ * PHY configurations are PPI based instead of port. ++ * Left: ++ * +---------+---------+---------+---------+--------+---------+----------+ ++ * | | | | | | | | ++ * | PPI | PPI5 | PPI4 | PPI3 | PPI2 | PPI1 | PPI0 | ++ * +---------+---------+---------+---------+--------+---------+----------+ ++ * | | | | | | | | ++ * | x4 | unused | D3 | D2 | C0 | D0 | D1 | ++ * |---------+---------+---------+---------+--------+---------+----------+ ++ * | | | | | | | | ++ * | x2x2 | C1 | D0 | D1 | C0 | D0 | D1 | ++ * ----------+---------+---------+---------+--------+---------+----------+ ++ * | | | | | | | | ++ * | x2x1 | C1 | D0 | unused | C0 | D0 | D1 | ++ * +---------+---------+---------+---------+--------+---------+----------+ ++ * | | | | | | | | ++ * | x1x1 | C1 | D0 | unused | C0 | D0 | unused | ++ * +---------+---------+---------+---------+--------+---------+----------+ ++ * | | | | | | | | ++ * | x1x2 | C1 | D0 | D1 | C0 | D0 | unused | ++ * +---------+---------+---------+---------+--------+---------+----------+ ++ * ++ * Right: ++ * +---------+---------+---------+---------+--------+---------+----------+ ++ * | | | | | | | | ++ * | PPI | PPI6 | PPI7 | PPI8 | PPI9 | PPI10 | PPI11 | ++ * +---------+---------+---------+---------+--------+---------+----------+ ++ * | | | | | | | | ++ * | x4 | D1 | D0 | C2 | D2 | D3 | unused | ++ * |---------+---------+---------+---------+--------+---------+----------+ ++ * | | | | | | | | ++ * | x2x2 | D1 | D0 | C2 | D1 | D0 | C3 | ++ * ----------+---------+---------+---------+--------+---------+----------+ ++ * | | | | | | | | ++ * | x2x1 | D1 | D0 | C2 | unused | D0 | C3 | ++ * +---------+---------+---------+---------+--------+---------+----------+ ++ * | | | | | | | | ++ * | x1x1 | unused | D0 | C2 | unused | D0 | C3 | ++ * +---------+---------+---------+---------+--------+---------+----------+ ++ * | | | | | | | | ++ * | x1x2 | unused | D0 | C2 | D1 | D0 | C3 | ++ * +---------+---------+---------+---------+--------+---------+----------+ ++ * ++ * ppi mapping per phy : ++ * ++ * x4 + x4: ++ * Left : port0 - PPI range {0, 1, 2, 3, 4} ++ * Right: port2 - PPI range {6, 7, 8, 9, 10} ++ * ++ * x4 + x2x2: ++ * Left: port0 - PPI range {0, 1, 2, 3, 4} ++ * Right: port2 - PPI range {6, 7, 8}, port3 - PPI range {9, 10, 11} ++ * ++ * x2x2 + x4: ++ * Left: port0 - PPI range {0, 1, 2}, port1 - PPI range {3, 4, 5} ++ * Right: port2 - PPI range {6, 7, 8, 9, 10} ++ * ++ * x2x2 + x2x2: ++ * Left : port0 - PPI range {0, 1, 2}, port1 - PPI range {3, 4, 5} ++ * Right: port2 - PPI range {6, 7, 8}, port3 - PPI range {9, 10, 11} ++ */ ++ ++struct phy_reg { ++ u32 reg; ++ u32 val; ++}; ++ ++static const struct phy_reg common_init_regs[] = { ++ /* for TGL-U, use 0x80000000 */ ++ {0x00000040, 0x80000000}, ++ {0x00000044, 0x00a80880}, ++ {0x00000044, 0x00b80880}, ++ {0x00000010, 0x0000078c}, ++ {0x00000344, 0x2f4401e2}, ++ {0x00000544, 0x924401e2}, ++ {0x00000744, 0x594401e2}, ++ {0x00000944, 0x624401e2}, ++ {0x00000b44, 0xfc4401e2}, ++ {0x00000d44, 0xc54401e2}, ++ {0x00000f44, 0x034401e2}, ++ {0x00001144, 0x8f4401e2}, ++ {0x00001344, 0x754401e2}, ++ {0x00001544, 0xe94401e2}, ++ {0x00001744, 0xcb4401e2}, ++ {0x00001944, 0xfa4401e2} ++}; ++ ++static const struct phy_reg x1_port0_config_regs[] = { ++ {0x00000694, 0xc80060fa}, ++ {0x00000680, 0x3d4f78ea}, ++ {0x00000690, 0x10a0140b}, ++ {0x000006a8, 0xdf04010a}, ++ {0x00000700, 0x57050060}, ++ {0x00000710, 0x0030001c}, ++ {0x00000738, 0x5f004444}, ++ {0x0000073c, 0x78464204}, ++ {0x00000748, 0x7821f940}, ++ {0x0000074c, 0xb2000433}, ++ {0x00000494, 0xfe6030fa}, ++ {0x00000480, 0x29ef5ed0}, ++ {0x00000490, 0x10a0540b}, ++ {0x000004a8, 0x7a01010a}, ++ {0x00000500, 0xef053460}, ++ {0x00000510, 0xe030101c}, ++ {0x00000538, 0xdf808444}, ++ {0x0000053c, 0xc8422204}, ++ {0x00000540, 0x0180088c}, ++ {0x00000574, 0x00000000}, ++ {0x00000000, 0x00000000} ++}; ++ ++static const struct phy_reg x1_port1_config_regs[] = { ++ {0x00000c94, 0xc80060fa}, ++ {0x00000c80, 0xcf47abea}, ++ {0x00000c90, 0x10a0840b}, ++ {0x00000ca8, 0xdf04010a}, ++ {0x00000d00, 0x57050060}, ++ {0x00000d10, 0x0030001c}, ++ {0x00000d38, 0x5f004444}, ++ {0x00000d3c, 0x78464204}, ++ {0x00000d48, 0x7821f940}, ++ {0x00000d4c, 0xb2000433}, ++ {0x00000a94, 0xc91030fa}, ++ {0x00000a80, 0x5a166ed0}, ++ {0x00000a90, 0x10a0540b}, ++ {0x00000aa8, 0x5d060100}, ++ {0x00000b00, 0xef053460}, ++ {0x00000b10, 0xa030101c}, ++ {0x00000b38, 0xdf808444}, ++ {0x00000b3c, 0xc8422204}, ++ {0x00000b40, 0x0180088c}, ++ {0x00000b74, 0x00000000}, ++ {0x00000000, 0x00000000} ++}; ++ ++static const struct phy_reg x1_port2_config_regs[] = { ++ {0x00001294, 0x28f000fa}, ++ {0x00001280, 0x08130cea}, ++ {0x00001290, 0x10a0140b}, ++ {0x000012a8, 0xd704010a}, ++ {0x00001300, 0x8d050060}, ++ {0x00001310, 0x0030001c}, ++ {0x00001338, 0xdf008444}, ++ {0x0000133c, 0x78422204}, ++ {0x00001348, 0x7821f940}, ++ {0x0000134c, 0x5a000433}, ++ {0x00001094, 0x2d20b0fa}, ++ {0x00001080, 0xade75dd0}, ++ {0x00001090, 0x10a0540b}, ++ {0x000010a8, 0xb101010a}, ++ {0x00001100, 0x33053460}, ++ {0x00001110, 0x0030101c}, ++ {0x00001138, 0xdf808444}, ++ {0x0000113c, 0xc8422204}, ++ {0x00001140, 0x8180088c}, ++ {0x00001174, 0x00000000}, ++ {0x00000000, 0x00000000} ++}; ++ ++static const struct phy_reg x1_port3_config_regs[] = { ++ {0x00001894, 0xc80060fa}, ++ {0x00001880, 0x0f90fd6a}, ++ {0x00001890, 0x10a0840b}, ++ {0x000018a8, 0xdf04010a}, ++ {0x00001900, 0x57050060}, ++ {0x00001910, 0x0030001c}, ++ {0x00001938, 0x5f004444}, ++ {0x0000193c, 0x78464204}, ++ {0x00001948, 0x7821f940}, ++ {0x0000194c, 0xb2000433}, ++ {0x00001694, 0x3050d0fa}, ++ {0x00001680, 0x0ef6d050}, ++ {0x00001690, 0x10a0540b}, ++ {0x000016a8, 0xe301010a}, ++ {0x00001700, 0x69053460}, ++ {0x00001710, 0xa030101c}, ++ {0x00001738, 0xdf808444}, ++ {0x0000173c, 0xc8422204}, ++ {0x00001740, 0x0180088c}, ++ {0x00001774, 0x00000000}, ++ {0x00000000, 0x00000000} ++}; ++ ++static const struct phy_reg x2_port0_config_regs[] = { ++ {0x00000694, 0xc80060fa}, ++ {0x00000680, 0x3d4f78ea}, ++ {0x00000690, 0x10a0140b}, ++ {0x000006a8, 0xdf04010a}, ++ {0x00000700, 0x57050060}, ++ {0x00000710, 0x0030001c}, ++ {0x00000738, 0x5f004444}, ++ {0x0000073c, 0x78464204}, ++ {0x00000748, 0x7821f940}, ++ {0x0000074c, 0xb2000433}, ++ {0x00000494, 0xc80060fa}, ++ {0x00000480, 0x29ef5ed8}, ++ {0x00000490, 0x10a0540b}, ++ {0x000004a8, 0x7a01010a}, ++ {0x00000500, 0xef053460}, ++ {0x00000510, 0xe030101c}, ++ {0x00000538, 0xdf808444}, ++ {0x0000053c, 0xc8422204}, ++ {0x00000540, 0x0180088c}, ++ {0x00000574, 0x00000000}, ++ {0x00000294, 0xc80060fa}, ++ {0x00000280, 0xcb45b950}, ++ {0x00000290, 0x10a0540b}, ++ {0x000002a8, 0x8c01010a}, ++ {0x00000300, 0xef053460}, ++ {0x00000310, 0x8030101c}, ++ {0x00000338, 0x41808444}, ++ {0x0000033c, 0x32422204}, ++ {0x00000340, 0x0180088c}, ++ {0x00000374, 0x00000000}, ++ {0x00000000, 0x00000000} ++}; ++ ++static const struct phy_reg x2_port1_config_regs[] = { ++ {0x00000c94, 0xc80060fa}, ++ {0x00000c80, 0xcf47abea}, ++ {0x00000c90, 0x10a0840b}, ++ {0x00000ca8, 0xdf04010a}, ++ {0x00000d00, 0x57050060}, ++ {0x00000d10, 0x0030001c}, ++ {0x00000d38, 0x5f004444}, ++ {0x00000d3c, 0x78464204}, ++ {0x00000d48, 0x7821f940}, ++ {0x00000d4c, 0xb2000433}, ++ {0x00000a94, 0xc80060fa}, ++ {0x00000a80, 0x5a166ed8}, ++ {0x00000a90, 0x10a0540b}, ++ {0x00000aa8, 0x7a01010a}, ++ {0x00000b00, 0xef053460}, ++ {0x00000b10, 0xa030101c}, ++ {0x00000b38, 0xdf808444}, ++ {0x00000b3c, 0xc8422204}, ++ {0x00000b40, 0x0180088c}, ++ {0x00000b74, 0x00000000}, ++ {0x00000894, 0xc80060fa}, ++ {0x00000880, 0x4d4f21d0}, ++ {0x00000890, 0x10a0540b}, ++ {0x000008a8, 0x5601010a}, ++ {0x00000900, 0xef053460}, ++ {0x00000910, 0x8030101c}, ++ {0x00000938, 0xdf808444}, ++ {0x0000093c, 0xc8422204}, ++ {0x00000940, 0x0180088c}, ++ {0x00000974, 0x00000000}, ++ {0x00000000, 0x00000000} ++}; ++ ++static const struct phy_reg x2_port2_config_regs[] = { ++ {0x00001294, 0xc80060fa}, ++ {0x00001280, 0x08130cea}, ++ {0x00001290, 0x10a0140b}, ++ {0x000012a8, 0xd704010a}, ++ {0x00001300, 0x8d050060}, ++ {0x00001310, 0x0030001c}, ++ {0x00001338, 0xdf008444}, ++ {0x0000133c, 0x78422204}, ++ {0x00001348, 0x7821f940}, ++ {0x0000134c, 0x5a000433}, ++ {0x00001094, 0xc80060fa}, ++ {0x00001080, 0xade75dd8}, ++ {0x00001090, 0x10a0540b}, ++ {0x000010a8, 0xb101010a}, ++ {0x00001100, 0x33053460}, ++ {0x00001110, 0x0030101c}, ++ {0x00001138, 0xdf808444}, ++ {0x0000113c, 0xc8422204}, ++ {0x00001140, 0x8180088c}, ++ {0x00001174, 0x00000000}, ++ {0x00000e94, 0xc80060fa}, ++ {0x00000e80, 0x0fbf16d0}, ++ {0x00000e90, 0x10a0540b}, ++ {0x00000ea8, 0x7a01010a}, ++ {0x00000f00, 0xf5053460}, ++ {0x00000f10, 0xc030101c}, ++ {0x00000f38, 0xdf808444}, ++ {0x00000f3c, 0xc8422204}, ++ {0x00000f40, 0x8180088c}, ++ {0x00000000, 0x00000000} ++}; ++ ++static const struct phy_reg x2_port3_config_regs[] = { ++ {0x00001894, 0xc80060fa}, ++ {0x00001880, 0x0f90fd6a}, ++ {0x00001890, 0x10a0840b}, ++ {0x000018a8, 0xdf04010a}, ++ {0x00001900, 0x57050060}, ++ {0x00001910, 0x0030001c}, ++ {0x00001938, 0x5f004444}, ++ {0x0000193c, 0x78464204}, ++ {0x00001948, 0x7821f940}, ++ {0x0000194c, 0xb2000433}, ++ {0x00001694, 0xc80060fa}, ++ {0x00001680, 0x0ef6d058}, ++ {0x00001690, 0x10a0540b}, ++ {0x000016a8, 0x7a01010a}, ++ {0x00001700, 0x69053460}, ++ {0x00001710, 0xa030101c}, ++ {0x00001738, 0xdf808444}, ++ {0x0000173c, 0xc8422204}, ++ {0x00001740, 0x0180088c}, ++ {0x00001774, 0x00000000}, ++ {0x00001494, 0xc80060fa}, ++ {0x00001480, 0xf9d34bd0}, ++ {0x00001490, 0x10a0540b}, ++ {0x000014a8, 0x7a01010a}, ++ {0x00001500, 0x1b053460}, ++ {0x00001510, 0x0030101c}, ++ {0x00001538, 0xdf808444}, ++ {0x0000153c, 0xc8422204}, ++ {0x00001540, 0x8180088c}, ++ {0x00001574, 0x00000000}, ++ {0x00000000, 0x00000000} ++}; ++ ++static const struct phy_reg x4_port0_config_regs[] = { ++ {0x00000694, 0xc80060fa}, ++ {0x00000680, 0x3d4f78fa}, ++ {0x00000690, 0x10a0140b}, ++ {0x000006a8, 0xdf04010a}, ++ {0x00000700, 0x57050060}, ++ {0x00000710, 0x0030001c}, ++ {0x00000738, 0x5f004444}, ++ {0x0000073c, 0x78464204}, ++ {0x00000748, 0x7821f940}, ++ {0x0000074c, 0xb2000433}, ++ {0x00000494, 0xfe6030fa}, ++ {0x00000480, 0x29ef5ed8}, ++ {0x00000490, 0x10a0540b}, ++ {0x000004a8, 0x7a01010a}, ++ {0x00000500, 0xef053460}, ++ {0x00000510, 0xe030101c}, ++ {0x00000538, 0xdf808444}, ++ {0x0000053c, 0xc8422204}, ++ {0x00000540, 0x0180088c}, ++ {0x00000574, 0x00000004}, ++ {0x00000294, 0x23e030fa}, ++ {0x00000280, 0xcb45b950}, ++ {0x00000290, 0x10a0540b}, ++ {0x000002a8, 0x8c01010a}, ++ {0x00000300, 0xef053460}, ++ {0x00000310, 0x8030101c}, ++ {0x00000338, 0x41808444}, ++ {0x0000033c, 0x32422204}, ++ {0x00000340, 0x0180088c}, ++ {0x00000374, 0x00000004}, ++ {0x00000894, 0x5620b0fa}, ++ {0x00000880, 0x4d4f21dc}, ++ {0x00000890, 0x10a0540b}, ++ {0x000008a8, 0x5601010a}, ++ {0x00000900, 0xef053460}, ++ {0x00000910, 0x8030101c}, ++ {0x00000938, 0xdf808444}, ++ {0x0000093c, 0xc8422204}, ++ {0x00000940, 0x0180088c}, ++ {0x00000974, 0x00000004}, ++ {0x00000a94, 0xc91030fa}, ++ {0x00000a80, 0x5a166ecc}, ++ {0x00000a90, 0x10a0540b}, ++ {0x00000aa8, 0x5d01010a}, ++ {0x00000b00, 0xef053460}, ++ {0x00000b10, 0xa030101c}, ++ {0x00000b38, 0xdf808444}, ++ {0x00000b3c, 0xc8422204}, ++ {0x00000b40, 0x0180088c}, ++ {0x00000b74, 0x00000004}, ++ {0x00000000, 0x00000000} ++}; ++ ++static const struct phy_reg x4_port1_config_regs[] = { ++ {0x00000000, 0x00000000} ++}; ++ ++static const struct phy_reg x4_port2_config_regs[] = { ++ {0x00001294, 0x28f000fa}, ++ {0x00001280, 0x08130cfa}, ++ {0x00001290, 0x10c0140b}, ++ {0x000012a8, 0xd704010a}, ++ {0x00001300, 0x8d050060}, ++ {0x00001310, 0x0030001c}, ++ {0x00001338, 0xdf008444}, ++ {0x0000133c, 0x78422204}, ++ {0x00001348, 0x7821f940}, ++ {0x0000134c, 0x5a000433}, ++ {0x00001094, 0x2d20b0fa}, ++ {0x00001080, 0xade75dd8}, ++ {0x00001090, 0x10a0540b}, ++ {0x000010a8, 0xb101010a}, ++ {0x00001100, 0x33053460}, ++ {0x00001110, 0x0030101c}, ++ {0x00001138, 0xdf808444}, ++ {0x0000113c, 0xc8422204}, ++ {0x00001140, 0x8180088c}, ++ {0x00001174, 0x00000004}, ++ {0x00000e94, 0xd308d0fa}, ++ {0x00000e80, 0x0fbf16d0}, ++ {0x00000e90, 0x10a0540b}, ++ {0x00000ea8, 0x2c01010a}, ++ {0x00000f00, 0xf5053460}, ++ {0x00000f10, 0xc030101c}, ++ {0x00000f38, 0xdf808444}, ++ {0x00000f3c, 0xc8422204}, ++ {0x00000f40, 0x8180088c}, ++ {0x00000f74, 0x00000004}, ++ {0x00001494, 0x136850fa}, ++ {0x00001480, 0xf9d34bdc}, ++ {0x00001490, 0x10a0540b}, ++ {0x000014a8, 0x5a01010a}, ++ {0x00001500, 0x1b053460}, ++ {0x00001510, 0x0030101c}, ++ {0x00001538, 0xdf808444}, ++ {0x0000153c, 0xc8422204}, ++ {0x00001540, 0x8180088c}, ++ {0x00001574, 0x00000004}, ++ {0x00001694, 0x3050d0fa}, ++ {0x00001680, 0x0ef6d04c}, ++ {0x00001690, 0x10a0540b}, ++ {0x000016a8, 0xe301010a}, ++ {0x00001700, 0x69053460}, ++ {0x00001710, 0xa030101c}, ++ {0x00001738, 0xdf808444}, ++ {0x0000173c, 0xc8422204}, ++ {0x00001740, 0x0180088c}, ++ {0x00001774, 0x00000004}, ++ {0x00000000, 0x00000000} ++}; ++ ++static const struct phy_reg x4_port3_config_regs[] = { ++ {0x00000000, 0x00000000} ++}; ++ ++static const struct phy_reg *x1_config_regs[4] = { ++ x1_port0_config_regs, ++ x1_port1_config_regs, ++ x1_port2_config_regs, ++ x1_port3_config_regs ++}; ++ ++static const struct phy_reg *x2_config_regs[4] = { ++ x2_port0_config_regs, ++ x2_port1_config_regs, ++ x2_port2_config_regs, ++ x2_port3_config_regs ++}; ++ ++static const struct phy_reg *x4_config_regs[4] = { ++ x4_port0_config_regs, ++ x4_port1_config_regs, ++ x4_port2_config_regs, ++ x4_port3_config_regs ++}; ++ ++static const struct phy_reg **config_regs[3] = { ++ x1_config_regs, ++ x2_config_regs, ++ x4_config_regs ++}; ++ ++static int ipu6_isys_mcd_phy_powerup_ack(struct ipu6_isys *isys, u8 id) ++{ ++ struct device *dev = &isys->adev->auxdev.dev; ++ void __iomem *isys_base = isys->pdata->base; ++ u32 val; ++ int ret; ++ ++ val = readl(isys_base + CSI_REG_HUB_GPREG_PHY_CTL(id)); ++ val |= CSI_REG_HUB_GPREG_PHY_CTL_PWR_EN; ++ writel(val, isys_base + CSI_REG_HUB_GPREG_PHY_CTL(id)); ++ ++ ret = readl_poll_timeout(isys_base + CSI_REG_HUB_GPREG_PHY_STATUS(id), ++ val, val & CSI_REG_HUB_GPREG_PHY_POWER_ACK, ++ 200, MCD_PHY_POWER_STATUS_TIMEOUT); ++ if (ret) ++ dev_err(dev, "PHY%d powerup ack timeout", id); ++ ++ return ret; ++} ++ ++static int ipu6_isys_mcd_phy_powerdown_ack(struct ipu6_isys *isys, u8 id) ++{ ++ struct device *dev = &isys->adev->auxdev.dev; ++ void __iomem *isys_base = isys->pdata->base; ++ u32 val; ++ int ret; ++ ++ writel(0, isys_base + CSI_REG_HUB_GPREG_PHY_CTL(id)); ++ ret = readl_poll_timeout(isys_base + CSI_REG_HUB_GPREG_PHY_STATUS(id), ++ val, !(val & CSI_REG_HUB_GPREG_PHY_POWER_ACK), ++ 200, MCD_PHY_POWER_STATUS_TIMEOUT); ++ if (ret) ++ dev_err(dev, "PHY%d powerdown ack timeout", id); ++ ++ return ret; ++} ++ ++static void ipu6_isys_mcd_phy_reset(struct ipu6_isys *isys, u8 id, bool assert) ++{ ++ void __iomem *isys_base = isys->pdata->base; ++ u32 val; ++ ++ val = readl(isys_base + CSI_REG_HUB_GPREG_PHY_CTL(id)); ++ if (assert) ++ val |= CSI_REG_HUB_GPREG_PHY_CTL_RESET; ++ else ++ val &= ~(CSI_REG_HUB_GPREG_PHY_CTL_RESET); ++ ++ writel(val, isys_base + CSI_REG_HUB_GPREG_PHY_CTL(id)); ++} ++ ++static int ipu6_isys_mcd_phy_ready(struct ipu6_isys *isys, u8 id) ++{ ++ struct device *dev = &isys->adev->auxdev.dev; ++ void __iomem *isys_base = isys->pdata->base; ++ u32 val; ++ int ret; ++ ++ ret = readl_poll_timeout(isys_base + CSI_REG_HUB_GPREG_PHY_STATUS(id), ++ val, val & CSI_REG_HUB_GPREG_PHY_READY, ++ 200, MCD_PHY_POWER_STATUS_TIMEOUT); ++ if (ret) ++ dev_err(dev, "PHY%d ready ack timeout", id); ++ ++ return ret; ++} ++ ++static void ipu6_isys_mcd_phy_common_init(struct ipu6_isys *isys) ++{ ++ struct ipu6_bus_device *adev = isys->adev; ++ struct ipu6_device *isp = adev->isp; ++ void __iomem *isp_base = isp->base; ++ struct sensor_async_sd *s_asd; ++ struct v4l2_async_connection *asc; ++ void __iomem *phy_base; ++ unsigned int i; ++ u8 phy_id; ++ ++ list_for_each_entry(asc, &isys->notifier.done_list, asc_entry) { ++ s_asd = container_of(asc, struct sensor_async_sd, asc); ++ phy_id = s_asd->csi2.port / 4; ++ phy_base = isp_base + IPU6_ISYS_MCD_PHY_BASE(phy_id); ++ ++ for (i = 0; i < ARRAY_SIZE(common_init_regs); i++) ++ writel(common_init_regs[i].val, ++ phy_base + common_init_regs[i].reg); ++ } ++} ++ ++static int ipu6_isys_driver_port_to_phy_port(struct ipu6_isys_csi2_config *cfg) ++{ ++ int phy_port; ++ int ret; ++ ++ if (!(cfg->nlanes == 4 || cfg->nlanes == 2 || cfg->nlanes == 1)) ++ return -EINVAL; ++ ++ /* B,F -> C0 A,E -> C1 C,G -> C2 D,H -> C4 */ ++ /* normalize driver port number */ ++ phy_port = cfg->port % 4; ++ ++ /* swap port number only for A and B */ ++ if (phy_port == 0) ++ phy_port = 1; ++ else if (phy_port == 1) ++ phy_port = 0; ++ ++ ret = phy_port; ++ ++ /* check validity per lane configuration */ ++ if (cfg->nlanes == 4 && !(phy_port == 0 || phy_port == 2)) ++ ret = -EINVAL; ++ else if ((cfg->nlanes == 2 || cfg->nlanes == 1) && ++ !(phy_port >= 0 && phy_port <= 3)) ++ ret = -EINVAL; ++ ++ return ret; ++} ++ ++static int ipu6_isys_mcd_phy_config(struct ipu6_isys *isys) ++{ ++ struct device *dev = &isys->adev->auxdev.dev; ++ struct ipu6_bus_device *adev = isys->adev; ++ const struct phy_reg **phy_config_regs; ++ struct ipu6_device *isp = adev->isp; ++ void __iomem *isp_base = isp->base; ++ struct sensor_async_sd *s_asd; ++ struct ipu6_isys_csi2_config cfg; ++ struct v4l2_async_connection *asc; ++ u8 phy_port, phy_id; ++ unsigned int i; ++ void __iomem *phy_base; ++ ++ list_for_each_entry(asc, &isys->notifier.done_list, asc_entry) { ++ s_asd = container_of(asc, struct sensor_async_sd, asc); ++ cfg.port = s_asd->csi2.port; ++ cfg.nlanes = s_asd->csi2.nlanes; ++ phy_port = ipu6_isys_driver_port_to_phy_port(&cfg); ++ if (phy_port < 0) { ++ dev_err(dev, "invalid port %d for lane %d", cfg.port, ++ cfg.nlanes); ++ return -ENXIO; ++ } ++ ++ phy_id = cfg.port / 4; ++ phy_base = isp_base + IPU6_ISYS_MCD_PHY_BASE(phy_id); ++ dev_dbg(dev, "port%d PHY%u lanes %u\n", cfg.port, phy_id, ++ cfg.nlanes); ++ ++ phy_config_regs = config_regs[cfg.nlanes / 2]; ++ cfg.port = phy_port; ++ for (i = 0; phy_config_regs[cfg.port][i].reg; i++) ++ writel(phy_config_regs[cfg.port][i].val, ++ phy_base + phy_config_regs[cfg.port][i].reg); ++ } ++ ++ return 0; ++} ++ ++#define CSI_MCD_PHY_NUM 2 ++static refcount_t phy_power_ref_count[CSI_MCD_PHY_NUM]; ++ ++int ipu6_isys_mcd_phy_set_power(struct ipu6_isys *isys, ++ struct ipu6_isys_csi2_config *cfg, ++ const struct ipu6_isys_csi2_timing *timing, ++ bool on) ++{ ++ struct device *dev = &isys->adev->auxdev.dev; ++ void __iomem *isys_base = isys->pdata->base; ++ u8 port, phy_id; ++ refcount_t *ref; ++ int ret; ++ ++ port = cfg->port; ++ phy_id = port / 4; ++ ++ ref = &phy_power_ref_count[phy_id]; ++ ++ dev_dbg(dev, "for phy %d port %d, lanes: %d\n", phy_id, port, ++ cfg->nlanes); ++ ++ if (!isys_base || port >= isys->pdata->ipdata->csi2.nports) { ++ dev_warn(dev, "invalid port ID %d\n", port); ++ return -EINVAL; ++ } ++ ++ if (on) { ++ if (refcount_read(ref)) { ++ dev_dbg(dev, "for phy %d is already UP", phy_id); ++ refcount_inc(ref); ++ return 0; ++ } ++ ++ ret = ipu6_isys_mcd_phy_powerup_ack(isys, phy_id); ++ if (ret) ++ return ret; ++ ++ ipu6_isys_mcd_phy_reset(isys, phy_id, 0); ++ ipu6_isys_mcd_phy_common_init(isys); ++ ++ ret = ipu6_isys_mcd_phy_config(isys); ++ if (ret) ++ return ret; ++ ++ ipu6_isys_mcd_phy_reset(isys, phy_id, 1); ++ ret = ipu6_isys_mcd_phy_ready(isys, phy_id); ++ if (ret) ++ return ret; ++ ++ refcount_set(ref, 1); ++ return 0; ++ } ++ ++ if (!refcount_dec_and_test(ref)) ++ return 0; ++ ++ return ipu6_isys_mcd_phy_powerdown_ack(isys, phy_id); ++} +-- +2.43.0 + +From a3b3658e56d3fe9e0cb03166e38a023dba853594 Mon Sep 17 00:00:00 2001 +From: Bingbu Cao <bingbu.cao@intel.com> +Date: Thu, 11 Jan 2024 14:55:24 +0800 +Subject: [PATCH 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 <bingbu.cao@intel.com> +Signed-off-by: Hans de Goede <hdegoede@redhat.com> +Link: https://lore.kernel.org/r/20240111065531.2418836-11-bingbu.cao@intel.com +Signed-off-by: Hans de Goede <hdegoede@redhat.com> +--- + drivers/media/pci/intel/ipu6/ipu6-isys.c | 1353 ++++++++++++++++++++++ + drivers/media/pci/intel/ipu6/ipu6-isys.h | 207 ++++ + 2 files changed, 1560 insertions(+) + create mode 100644 drivers/media/pci/intel/ipu6/ipu6-isys.c + create mode 100644 drivers/media/pci/intel/ipu6/ipu6-isys.h + +diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys.c b/drivers/media/pci/intel/ipu6/ipu6-isys.c +new file mode 100644 +index 000000000000..e8983363a0da +--- /dev/null ++++ b/drivers/media/pci/intel/ipu6/ipu6-isys.c +@@ -0,0 +1,1353 @@ ++// SPDX-License-Identifier: GPL-2.0-only ++/* ++ * Copyright (C) 2013 - 2023 Intel Corporation ++ */ ++ ++#include <linux/auxiliary_bus.h> ++#include <linux/bitfield.h> ++#include <linux/bits.h> ++#include <linux/completion.h> ++#include <linux/container_of.h> ++#include <linux/delay.h> ++#include <linux/device.h> ++#include <linux/dma-mapping.h> ++#include <linux/err.h> ++#include <linux/firmware.h> ++#include <linux/io.h> ++#include <linux/irqreturn.h> ++#include <linux/list.h> ++#include <linux/module.h> ++#include <linux/mutex.h> ++#include <linux/pci.h> ++#include <linux/pm_runtime.h> ++#include <linux/pm_qos.h> ++#include <linux/slab.h> ++#include <linux/spinlock.h> ++#include <linux/string.h> ++ ++#include <media/ipu-bridge.h> ++#include <media/media-device.h> ++#include <media/media-entity.h> ++#include <media/v4l2-async.h> ++#include <media/v4l2-device.h> ++#include <media/v4l2-fwnode.h> ++ ++#include "ipu6-bus.h" ++#include "ipu6-cpd.h" ++#include "ipu6-isys.h" ++#include "ipu6-isys-csi2.h" ++#include "ipu6-mmu.h" ++#include "ipu6-platform-buttress-regs.h" ++#include "ipu6-platform-isys-csi2-reg.h" ++#include "ipu6-platform-regs.h" ++ ++#define IPU6_BUTTRESS_FABIC_CONTROL 0x68 ++#define GDA_ENABLE_IWAKE_INDEX 2 ++#define GDA_IWAKE_THRESHOLD_INDEX 1 ++#define GDA_IRQ_CRITICAL_THRESHOLD_INDEX 0 ++#define GDA_MEMOPEN_THRESHOLD_INDEX 3 ++#define DEFAULT_DID_RATIO 90 ++#define DEFAULT_IWAKE_THRESHOLD 0x42 ++#define DEFAULT_MEM_OPEN_TIME 10 ++#define ONE_THOUSAND_MICROSECOND 1000 ++/* One page is 2KB, 8 x 16 x 16 = 2048B = 2KB */ ++#define ISF_DMA_TOP_GDA_PROFERTY_PAGE_SIZE 0x800 ++ ++/* LTR & DID value are 10 bit at most */ ++#define LTR_DID_VAL_MAX 1023 ++#define LTR_DEFAULT_VALUE 0x70503c19 ++#define FILL_TIME_DEFAULT_VALUE 0xfff0783c ++#define LTR_DID_PKGC_2R 20 ++#define LTR_SCALE_DEFAULT 5 ++#define LTR_SCALE_1024NS 2 ++#define DID_SCALE_1US 2 ++#define DID_SCALE_32US 3 ++#define REG_PKGC_PMON_CFG 0xb00 ++ ++#define VAL_PKGC_PMON_CFG_RESET 0x38 ++#define VAL_PKGC_PMON_CFG_START 0x7 ++ ++#define IS_PIXEL_BUFFER_PAGES 0x80 ++/* ++ * when iwake mode is disabled, the critical threshold is statically set ++ * to 75% of the IS pixel buffer, criticalThreshold = (128 * 3) / 4 ++ */ ++#define CRITICAL_THRESHOLD_IWAKE_DISABLE (IS_PIXEL_BUFFER_PAGES * 3 / 4) ++ ++union fabric_ctrl { ++ struct { ++ u16 ltr_val : 10; ++ u16 ltr_scale : 3; ++ u16 reserved : 3; ++ u16 did_val : 10; ++ u16 did_scale : 3; ++ u16 reserved2 : 1; ++ u16 keep_power_in_D0 : 1; ++ u16 keep_power_override : 1; ++ } bits; ++ u32 value; ++}; ++ ++enum ltr_did_type { ++ LTR_IWAKE_ON, ++ LTR_IWAKE_OFF, ++ LTR_ISYS_ON, ++ LTR_ISYS_OFF, ++ LTR_ENHANNCE_IWAKE, ++ LTR_TYPE_MAX ++}; ++ ++#define ISYS_PM_QOS_VALUE 300 ++ ++static int isys_isr_one(struct ipu6_bus_device *adev); ++ ++static int ++isys_complete_ext_device_registration(struct ipu6_isys *isys, ++ struct v4l2_subdev *sd, ++ struct ipu6_isys_csi2_config *csi2) ++{ ++ struct device *dev = &isys->adev->auxdev.dev; ++ unsigned int i; ++ int ret; ++ ++ for (i = 0; i < sd->entity.num_pads; i++) { ++ if (sd->entity.pads[i].flags & MEDIA_PAD_FL_SOURCE) ++ break; ++ } ++ ++ if (i == sd->entity.num_pads) { ++ dev_warn(dev, "no src pad in external entity\n"); ++ ret = -ENOENT; ++ goto unregister_subdev; ++ } ++ ++ ret = media_create_pad_link(&sd->entity, i, ++ &isys->csi2[csi2->port].asd.sd.entity, ++ 0, 0); ++ if (ret) { ++ dev_warn(dev, "can't create link\n"); ++ goto unregister_subdev; ++ } ++ ++ isys->csi2[csi2->port].nlanes = csi2->nlanes; ++ ++ return 0; ++ ++unregister_subdev: ++ v4l2_device_unregister_subdev(sd); ++ ++ return ret; ++} ++ ++static void isys_stream_init(struct ipu6_isys *isys) ++{ ++ u32 i; ++ ++ for (i = 0; i < IPU6_ISYS_MAX_STREAMS; i++) { ++ mutex_init(&isys->streams[i].mutex); ++ init_completion(&isys->streams[i].stream_open_completion); ++ init_completion(&isys->streams[i].stream_close_completion); ++ init_completion(&isys->streams[i].stream_start_completion); ++ init_completion(&isys->streams[i].stream_stop_completion); ++ INIT_LIST_HEAD(&isys->streams[i].queues); ++ isys->streams[i].isys = isys; ++ isys->streams[i].stream_handle = i; ++ isys->streams[i].vc = INVALID_VC_ID; ++ } ++} ++ ++static void isys_csi2_unregister_subdevices(struct ipu6_isys *isys) ++{ ++ const struct ipu6_isys_internal_csi2_pdata *csi2 = ++ &isys->pdata->ipdata->csi2; ++ unsigned int i; ++ ++ for (i = 0; i < csi2->nports; i++) ++ ipu6_isys_csi2_cleanup(&isys->csi2[i]); ++} ++ ++static int isys_csi2_register_subdevices(struct ipu6_isys *isys) ++{ ++ const struct ipu6_isys_internal_csi2_pdata *csi2_pdata = ++ &isys->pdata->ipdata->csi2; ++ struct device *dev = &isys->adev->auxdev.dev; ++ unsigned int i; ++ int ret; ++ ++ isys->csi2 = devm_kcalloc(dev, csi2_pdata->nports, ++ sizeof(*isys->csi2), GFP_KERNEL); ++ if (!isys->csi2) ++ return -ENOMEM; ++ ++ for (i = 0; i < csi2_pdata->nports; i++) { ++ ret = ipu6_isys_csi2_init(&isys->csi2[i], isys, ++ isys->pdata->base + ++ csi2_pdata->offsets[i], i); ++ if (ret) ++ goto fail; ++ ++ isys->isr_csi2_bits |= IPU6_ISYS_UNISPART_IRQ_CSI2(i); ++ } ++ ++ return 0; ++ ++fail: ++ while (i--) ++ ipu6_isys_csi2_cleanup(&isys->csi2[i]); ++ ++ return ret; ++} ++ ++static int isys_csi2_create_media_links(struct ipu6_isys *isys) ++{ ++ const struct ipu6_isys_internal_csi2_pdata *csi2_pdata = ++ &isys->pdata->ipdata->csi2; ++ struct device *dev = &isys->adev->auxdev.dev; ++ unsigned int i, j, k; ++ int ret; ++ ++ for (i = 0; i < csi2_pdata->nports; i++) { ++ struct media_entity *sd = &isys->csi2[i].asd.sd.entity; ++ ++ for (j = 0; j < NR_OF_VIDEO_DEVICE; j++) { ++ struct media_entity *v = &isys->av[j].vdev.entity; ++ u32 flag = MEDIA_LNK_FL_DYNAMIC; ++ ++ for (k = CSI2_PAD_SRC; k < NR_OF_CSI2_PADS; k++) { ++ ret = media_create_pad_link(sd, k, v, 0, flag); ++ if (ret) { ++ dev_err(dev, "CSI2 can't create link\n"); ++ return ret; ++ } ++ } ++ } ++ } ++ ++ return 0; ++} ++ ++static void isys_unregister_video_devices(struct ipu6_isys *isys) ++{ ++ unsigned int i; ++ ++ for (i = 0; i < NR_OF_VIDEO_DEVICE; i++) ++ ipu6_isys_video_cleanup(&isys->av[i]); ++} ++ ++static int isys_register_video_devices(struct ipu6_isys *isys) ++{ ++ unsigned int i; ++ int ret; ++ ++ for (i = 0; i < NR_OF_VIDEO_DEVICE; i++) { ++ snprintf(isys->av[i].vdev.name, sizeof(isys->av[i].vdev.name), ++ IPU6_ISYS_ENTITY_PREFIX " ISYS Capture %u", i); ++ isys->av[i].isys = isys; ++ isys->av[i].aq.vbq.buf_struct_size = ++ sizeof(struct ipu6_isys_video_buffer); ++ isys->av[i].pfmt = &ipu6_isys_pfmts[0]; ++ ++ ret = ipu6_isys_video_init(&isys->av[i]); ++ if (ret) ++ goto fail; ++ } ++ ++ return 0; ++ ++fail: ++ while (i--) ++ ipu6_isys_video_cleanup(&isys->av[i]); ++ ++ return ret; ++} ++ ++void isys_setup_hw(struct ipu6_isys *isys) ++{ ++ void __iomem *base = isys->pdata->base; ++ const u8 *thd = isys->pdata->ipdata->hw_variant.cdc_fifo_threshold; ++ u32 irqs = 0; ++ unsigned int i, nports; ++ ++ nports = isys->pdata->ipdata->csi2.nports; ++ ++ /* Enable irqs for all MIPI ports */ ++ for (i = 0; i < nports; i++) ++ irqs |= IPU6_ISYS_UNISPART_IRQ_CSI2(i); ++ ++ writel(irqs, base + isys->pdata->ipdata->csi2.ctrl0_irq_edge); ++ writel(irqs, base + isys->pdata->ipdata->csi2.ctrl0_irq_lnp); ++ writel(irqs, base + isys->pdata->ipdata->csi2.ctrl0_irq_mask); ++ writel(irqs, base + isys->pdata->ipdata->csi2.ctrl0_irq_enable); ++ writel(GENMASK(19, 0), ++ base + isys->pdata->ipdata->csi2.ctrl0_irq_clear); ++ ++ irqs = ISYS_UNISPART_IRQS; ++ writel(irqs, base + IPU6_REG_ISYS_UNISPART_IRQ_EDGE); ++ writel(irqs, base + IPU6_REG_ISYS_UNISPART_IRQ_LEVEL_NOT_PULSE); ++ writel(GENMASK(28, 0), base + IPU6_REG_ISYS_UNISPART_IRQ_CLEAR); ++ writel(irqs, base + IPU6_REG_ISYS_UNISPART_IRQ_MASK); ++ writel(irqs, base + IPU6_REG_ISYS_UNISPART_IRQ_ENABLE); ++ ++ writel(0, base + IPU6_REG_ISYS_UNISPART_SW_IRQ_REG); ++ writel(0, base + IPU6_REG_ISYS_UNISPART_SW_IRQ_MUX_REG); ++ ++ /* Write CDC FIFO threshold values for isys */ ++ for (i = 0; i < isys->pdata->ipdata->hw_variant.cdc_fifos; i++) ++ writel(thd[i], base + IPU6_REG_ISYS_CDC_THRESHOLD(i)); ++} ++ ++static void ipu6_isys_csi2_isr(struct ipu6_isys_csi2 *csi2) ++{ ++ struct ipu6_isys_stream *stream; ++ unsigned int i; ++ u32 status; ++ int source; ++ ++ ipu6_isys_register_errors(csi2); ++ ++ status = readl(csi2->base + CSI_PORT_REG_BASE_IRQ_CSI_SYNC + ++ CSI_PORT_REG_BASE_IRQ_STATUS_OFFSET); ++ ++ writel(status, csi2->base + CSI_PORT_REG_BASE_IRQ_CSI_SYNC + ++ CSI_PORT_REG_BASE_IRQ_CLEAR_OFFSET); ++ ++ source = csi2->asd.source; ++ for (i = 0; i < NR_OF_CSI2_VC; i++) { ++ if (status & IPU_CSI_RX_IRQ_FS_VC(i)) { ++ stream = ipu6_isys_query_stream_by_source(csi2->isys, ++ source, i); ++ if (stream) { ++ ipu6_isys_csi2_sof_event_by_stream(stream); ++ ipu6_isys_put_stream(stream); ++ } ++ } ++ ++ if (status & IPU_CSI_RX_IRQ_FE_VC(i)) { ++ stream = ipu6_isys_query_stream_by_source(csi2->isys, ++ source, i); ++ if (stream) { ++ ipu6_isys_csi2_eof_event_by_stream(stream); ++ ipu6_isys_put_stream(stream); ++ } ++ } ++ } ++} ++ ++irqreturn_t isys_isr(struct ipu6_bus_device *adev) ++{ ++ struct ipu6_isys *isys = ipu6_bus_get_drvdata(adev); ++ void __iomem *base = isys->pdata->base; ++ u32 status_sw, status_csi; ++ u32 ctrl0_status, ctrl0_clear; ++ ++ spin_lock(&isys->power_lock); ++ if (!isys->power) { ++ spin_unlock(&isys->power_lock); ++ return IRQ_NONE; ++ } ++ ++ ctrl0_status = isys->pdata->ipdata->csi2.ctrl0_irq_status; ++ ctrl0_clear = isys->pdata->ipdata->csi2.ctrl0_irq_clear; ++ ++ status_csi = readl(isys->pdata->base + ctrl0_status); ++ status_sw = readl(isys->pdata->base + ++ IPU6_REG_ISYS_UNISPART_IRQ_STATUS); ++ ++ writel(ISYS_UNISPART_IRQS & ~IPU6_ISYS_UNISPART_IRQ_SW, ++ base + IPU6_REG_ISYS_UNISPART_IRQ_MASK); ++ ++ do { ++ writel(status_csi, isys->pdata->base + ctrl0_clear); ++ ++ writel(status_sw, isys->pdata->base + ++ IPU6_REG_ISYS_UNISPART_IRQ_CLEAR); ++ ++ if (isys->isr_csi2_bits & status_csi) { ++ unsigned int i; ++ ++ for (i = 0; i < isys->pdata->ipdata->csi2.nports; i++) { ++ /* irq from not enabled port */ ++ if (!isys->csi2[i].base) ++ continue; ++ if (status_csi & IPU6_ISYS_UNISPART_IRQ_CSI2(i)) ++ ipu6_isys_csi2_isr(&isys->csi2[i]); ++ } ++ } ++ ++ writel(0, base + IPU6_REG_ISYS_UNISPART_SW_IRQ_REG); ++ ++ if (!isys_isr_one(adev)) ++ status_sw = IPU6_ISYS_UNISPART_IRQ_SW; ++ else ++ status_sw = 0; ++ ++ status_csi = readl(isys->pdata->base + ctrl0_status); ++ status_sw |= readl(isys->pdata->base + ++ IPU6_REG_ISYS_UNISPART_IRQ_STATUS); ++ } while ((status_csi & isys->isr_csi2_bits) || ++ (status_sw & IPU6_ISYS_UNISPART_IRQ_SW)); ++ ++ writel(ISYS_UNISPART_IRQS, base + IPU6_REG_ISYS_UNISPART_IRQ_MASK); ++ ++ spin_unlock(&isys->power_lock); ++ ++ return IRQ_HANDLED; ++} ++ ++static void get_lut_ltrdid(struct ipu6_isys *isys, struct ltr_did *pltr_did) ++{ ++ struct isys_iwake_watermark *iwake_watermark = &isys->iwake_watermark; ++ struct ltr_did ltrdid_default; ++ ++ ltrdid_default.lut_ltr.value = LTR_DEFAULT_VALUE; ++ ltrdid_default.lut_fill_time.value = FILL_TIME_DEFAULT_VALUE; ++ ++ if (iwake_watermark->ltrdid.lut_ltr.value) ++ *pltr_did = iwake_watermark->ltrdid; ++ else ++ *pltr_did = ltrdid_default; ++} ++ ++static int set_iwake_register(struct ipu6_isys *isys, u32 index, u32 value) ++{ ++ struct device *dev = &isys->adev->auxdev.dev; ++ u32 req_id = index; ++ u32 offset = 0; ++ int ret; ++ ++ ret = ipu6_fw_isys_send_proxy_token(isys, req_id, index, offset, value); ++ if (ret) ++ dev_err(dev, "write %d failed %d", index, ret); ++ ++ return ret; ++} ++ ++/* ++ * When input system is powered up and before enabling any new sensor capture, ++ * or after disabling any sensor capture the following values need to be set: ++ * LTR_value = LTR(usec) from calculation; ++ * LTR_scale = 2; ++ * DID_value = DID(usec) from calculation; ++ * DID_scale = 2; ++ * ++ * When input system is powered down, the LTR and DID values ++ * must be returned to the default values: ++ * LTR_value = 1023; ++ * LTR_scale = 5; ++ * DID_value = 1023; ++ * DID_scale = 2; ++ */ ++static void set_iwake_ltrdid(struct ipu6_isys *isys, u16 ltr, u16 did, ++ enum ltr_did_type use) ++{ ++ struct device *dev = &isys->adev->auxdev.dev; ++ u16 ltr_val, ltr_scale = LTR_SCALE_1024NS; ++ u16 did_val, did_scale = DID_SCALE_1US; ++ struct ipu6_device *isp = isys->adev->isp; ++ union fabric_ctrl fc; ++ ++ switch (use) { ++ case LTR_IWAKE_ON: ++ ltr_val = min_t(u16, ltr, (u16)LTR_DID_VAL_MAX); ++ did_val = min_t(u16, did, (u16)LTR_DID_VAL_MAX); ++ ltr_scale = (ltr == LTR_DID_VAL_MAX && ++ did == LTR_DID_VAL_MAX) ? ++ LTR_SCALE_DEFAULT : LTR_SCALE_1024NS; ++ break; ++ case LTR_ISYS_ON: ++ case LTR_IWAKE_OFF: ++ ltr_val = LTR_DID_PKGC_2R; ++ did_val = LTR_DID_PKGC_2R; ++ break; ++ case LTR_ISYS_OFF: ++ ltr_val = LTR_DID_VAL_MAX; ++ did_val = LTR_DID_VAL_MAX; ++ ltr_scale = LTR_SCALE_DEFAULT; ++ break; ++ case LTR_ENHANNCE_IWAKE: ++ if (ltr == LTR_DID_VAL_MAX && did == LTR_DID_VAL_MAX) { ++ ltr_val = LTR_DID_VAL_MAX; ++ did_val = LTR_DID_VAL_MAX; ++ ltr_scale = LTR_SCALE_DEFAULT; ++ } else if (did < ONE_THOUSAND_MICROSECOND) { ++ ltr_val = ltr; ++ did_val = did; ++ } else { ++ ltr_val = ltr; ++ /* div 90% value by 32 to account for scale change */ ++ did_val = did / 32; ++ did_scale = DID_SCALE_32US; ++ } ++ break; ++ default: ++ ltr_val = LTR_DID_VAL_MAX; ++ did_val = LTR_DID_VAL_MAX; ++ ltr_scale = LTR_SCALE_DEFAULT; ++ break; ++ } ++ ++ fc.value = readl(isp->base + IPU6_BUTTRESS_FABIC_CONTROL); ++ fc.bits.ltr_val = ltr_val; ++ fc.bits.ltr_scale = ltr_scale; ++ fc.bits.did_val = did_val; ++ fc.bits.did_scale = did_scale; ++ ++ dev_dbg(dev, "ltr: value %u scale %u, did: value %u scale %u\n", ++ ltr_val, ltr_scale, did_val, did_scale); ++ writel(fc.value, isp->base + IPU6_BUTTRESS_FABIC_CONTROL); ++} ++ ++/* ++ * Driver may clear register GDA_ENABLE_IWAKE before FW configures the ++ * stream for debug purpose. Otherwise driver should not access this register. ++ */ ++static void enable_iwake(struct ipu6_isys *isys, bool enable) ++{ ++ struct isys_iwake_watermark *iwake_watermark = &isys->iwake_watermark; ++ int ret; ++ ++ mutex_lock(&iwake_watermark->mutex); ++ ++ if (iwake_watermark->iwake_enabled == enable) { ++ mutex_unlock(&iwake_watermark->mutex); ++ return; ++ } ++ ++ ret = set_iwake_register(isys, GDA_ENABLE_IWAKE_INDEX, enable); ++ if (!ret) ++ iwake_watermark->iwake_enabled = enable; ++ ++ mutex_unlock(&iwake_watermark->mutex); ++} ++ ++void update_watermark_setting(struct ipu6_isys *isys) ++{ ++ struct isys_iwake_watermark *iwake_watermark = &isys->iwake_watermark; ++ u32 iwake_threshold, iwake_critical_threshold, page_num; ++ struct device *dev = &isys->adev->auxdev.dev; ++ u32 calc_fill_time_us = 0, ltr = 0, did = 0; ++ struct video_stream_watermark *p_watermark; ++ enum ltr_did_type ltr_did_type; ++ struct list_head *stream_node; ++ u64 isys_pb_datarate_mbs = 0; ++ u32 mem_open_threshold = 0; ++ struct ltr_did ltrdid; ++ u64 threshold_bytes; ++ u32 max_sram_size; ++ u32 shift; ++ ++ shift = isys->pdata->ipdata->sram_gran_shift; ++ max_sram_size = isys->pdata->ipdata->max_sram_size; ++ ++ mutex_lock(&iwake_watermark->mutex); ++ if (iwake_watermark->force_iwake_disable) { ++ set_iwake_ltrdid(isys, 0, 0, LTR_IWAKE_OFF); ++ set_iwake_register(isys, GDA_IRQ_CRITICAL_THRESHOLD_INDEX, ++ CRITICAL_THRESHOLD_IWAKE_DISABLE); ++ goto unlock_exit; ++ } ++ ++ if (list_empty(&iwake_watermark->video_list)) { ++ isys_pb_datarate_mbs = 0; ++ } else { ++ list_for_each(stream_node, &iwake_watermark->video_list) { ++ p_watermark = list_entry(stream_node, ++ struct video_stream_watermark, ++ stream_node); ++ isys_pb_datarate_mbs += p_watermark->stream_data_rate; ++ } ++ } ++ mutex_unlock(&iwake_watermark->mutex); ++ ++ if (!isys_pb_datarate_mbs) { ++ enable_iwake(isys, false); ++ set_iwake_ltrdid(isys, 0, 0, LTR_IWAKE_OFF); ++ mutex_lock(&iwake_watermark->mutex); ++ set_iwake_register(isys, GDA_IRQ_CRITICAL_THRESHOLD_INDEX, ++ CRITICAL_THRESHOLD_IWAKE_DISABLE); ++ goto unlock_exit; ++ } ++ ++ enable_iwake(isys, true); ++ calc_fill_time_us = max_sram_size / isys_pb_datarate_mbs; ++ ++ if (isys->pdata->ipdata->enhanced_iwake) { ++ ltr = isys->pdata->ipdata->ltr; ++ did = calc_fill_time_us * DEFAULT_DID_RATIO / 100; ++ ltr_did_type = LTR_ENHANNCE_IWAKE; ++ } else { ++ get_lut_ltrdid(isys, <rdid); ++ ++ if (calc_fill_time_us <= ltrdid.lut_fill_time.bits.th0) ++ ltr = 0; ++ else if (calc_fill_time_us <= ltrdid.lut_fill_time.bits.th1) ++ ltr = ltrdid.lut_ltr.bits.val0; ++ else if (calc_fill_time_us <= ltrdid.lut_fill_time.bits.th2) ++ ltr = ltrdid.lut_ltr.bits.val1; ++ else if (calc_fill_time_us <= ltrdid.lut_fill_time.bits.th3) ++ ltr = ltrdid.lut_ltr.bits.val2; ++ else ++ ltr = ltrdid.lut_ltr.bits.val3; ++ ++ did = calc_fill_time_us - ltr; ++ ltr_did_type = LTR_IWAKE_ON; ++ } ++ ++ set_iwake_ltrdid(isys, ltr, did, ltr_did_type); ++ ++ /* calculate iwake threshold with 2KB granularity pages */ ++ threshold_bytes = did * isys_pb_datarate_mbs; ++ iwake_threshold = max_t(u32, 1, threshold_bytes >> shift); ++ iwake_threshold = min_t(u32, iwake_threshold, max_sram_size); ++ ++ mutex_lock(&iwake_watermark->mutex); ++ if (isys->pdata->ipdata->enhanced_iwake) { ++ set_iwake_register(isys, GDA_IWAKE_THRESHOLD_INDEX, ++ DEFAULT_IWAKE_THRESHOLD); ++ /* calculate number of pages that will be filled in 10 usec */ ++ page_num = (DEFAULT_MEM_OPEN_TIME * isys_pb_datarate_mbs) / ++ ISF_DMA_TOP_GDA_PROFERTY_PAGE_SIZE; ++ page_num += ((DEFAULT_MEM_OPEN_TIME * isys_pb_datarate_mbs) % ++ ISF_DMA_TOP_GDA_PROFERTY_PAGE_SIZE) ? 1 : 0; ++ mem_open_threshold = isys->pdata->ipdata->memopen_threshold; ++ mem_open_threshold = max_t(u32, mem_open_threshold, page_num); ++ dev_dbg(dev, "mem_open_threshold: %u\n", mem_open_threshold); ++ set_iwake_register(isys, GDA_MEMOPEN_THRESHOLD_INDEX, ++ mem_open_threshold); ++ } else { ++ set_iwake_register(isys, GDA_IWAKE_THRESHOLD_INDEX, ++ iwake_threshold); ++ } ++ ++ iwake_critical_threshold = iwake_threshold + ++ (IS_PIXEL_BUFFER_PAGES - iwake_threshold) / 2; ++ ++ dev_dbg(dev, "threshold: %u critical: %u\n", iwake_threshold, ++ iwake_critical_threshold); ++ ++ set_iwake_register(isys, GDA_IRQ_CRITICAL_THRESHOLD_INDEX, ++ iwake_critical_threshold); ++ ++ writel(VAL_PKGC_PMON_CFG_RESET, ++ isys->adev->isp->base + REG_PKGC_PMON_CFG); ++ writel(VAL_PKGC_PMON_CFG_START, ++ isys->adev->isp->base + REG_PKGC_PMON_CFG); ++unlock_exit: ++ mutex_unlock(&iwake_watermark->mutex); ++} ++ ++static void isys_iwake_watermark_init(struct ipu6_isys *isys) ++{ ++ struct isys_iwake_watermark *iwake_watermark = &isys->iwake_watermark; ++ ++ INIT_LIST_HEAD(&iwake_watermark->video_list); ++ mutex_init(&iwake_watermark->mutex); ++ ++ iwake_watermark->ltrdid.lut_ltr.value = 0; ++ iwake_watermark->isys = isys; ++ iwake_watermark->iwake_enabled = false; ++ iwake_watermark->force_iwake_disable = false; ++} ++ ++static void isys_iwake_watermark_cleanup(struct ipu6_isys *isys) ++{ ++ struct isys_iwake_watermark *iwake_watermark = &isys->iwake_watermark; ++ ++ mutex_lock(&iwake_watermark->mutex); ++ list_del(&iwake_watermark->video_list); ++ mutex_unlock(&iwake_watermark->mutex); ++ ++ mutex_destroy(&iwake_watermark->mutex); ++} ++ ++/* The .bound() notifier callback when a match is found */ ++static int isys_notifier_bound(struct v4l2_async_notifier *notifier, ++ struct v4l2_subdev *sd, ++ struct v4l2_async_connection *asc) ++{ ++ struct ipu6_isys *isys = ++ container_of(notifier, struct ipu6_isys, notifier); ++ struct sensor_async_sd *s_asd = ++ container_of(asc, struct sensor_async_sd, asc); ++ int ret; ++ ++ ret = ipu_bridge_instantiate_vcm(sd->dev); ++ if (ret) { ++ dev_err(&isys->adev->auxdev.dev, "instantiate vcm failed\n"); ++ return ret; ++ } ++ ++ dev_dbg(&isys->adev->auxdev.dev, "bind %s nlanes is %d port is %d\n", ++ sd->name, s_asd->csi2.nlanes, s_asd->csi2.port); ++ ret = isys_complete_ext_device_registration(isys, sd, &s_asd->csi2); ++ if (ret) ++ return ret; ++ ++ return v4l2_device_register_subdev_nodes(&isys->v4l2_dev); ++} ++ ++static int isys_notifier_complete(struct v4l2_async_notifier *notifier) ++{ ++ struct ipu6_isys *isys = ++ container_of(notifier, struct ipu6_isys, notifier); ++ ++ return v4l2_device_register_subdev_nodes(&isys->v4l2_dev); ++} ++ ++static const struct v4l2_async_notifier_operations isys_async_ops = { ++ .bound = isys_notifier_bound, ++ .complete = isys_notifier_complete, ++}; ++ ++#define ISYS_MAX_PORTS 8 ++static int isys_notifier_init(struct ipu6_isys *isys) ++{ ++ struct ipu6_device *isp = isys->adev->isp; ++ struct device *dev = &isp->pdev->dev; ++ unsigned int i; ++ int ret; ++ ++ v4l2_async_nf_init(&isys->notifier, &isys->v4l2_dev); ++ ++ for (i = 0; i < ISYS_MAX_PORTS; i++) { ++ struct v4l2_fwnode_endpoint vep = { ++ .bus_type = V4L2_MBUS_CSI2_DPHY ++ }; ++ struct sensor_async_sd *s_asd; ++ struct fwnode_handle *ep; ++ ++ ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev), i, 0, ++ FWNODE_GRAPH_ENDPOINT_NEXT); ++ if (!ep) ++ continue; ++ ++ ret = v4l2_fwnode_endpoint_parse(ep, &vep); ++ if (ret) { ++ dev_err(dev, "fwnode endpoint parse failed: %d\n", ret); ++ goto err_parse; ++ } ++ ++ s_asd = v4l2_async_nf_add_fwnode_remote(&isys->notifier, ep, ++ struct sensor_async_sd); ++ if (IS_ERR(s_asd)) { ++ ret = PTR_ERR(s_asd); ++ dev_err(dev, "add remove fwnode failed: %d\n", ret); ++ goto err_parse; ++ } ++ ++ s_asd->csi2.port = vep.base.port; ++ s_asd->csi2.nlanes = vep.bus.mipi_csi2.num_data_lanes; ++ ++ dev_dbg(dev, "remote endpoint port %d with %d lanes added\n", ++ s_asd->csi2.port, s_asd->csi2.nlanes); ++ ++ fwnode_handle_put(ep); ++ ++ continue; ++ ++err_parse: ++ fwnode_handle_put(ep); ++ return ret; ++ } ++ ++ isys->notifier.ops = &isys_async_ops; ++ ret = v4l2_async_nf_register(&isys->notifier); ++ if (ret) { ++ dev_err(dev, "failed to register async notifier : %d\n", ret); ++ v4l2_async_nf_cleanup(&isys->notifier); ++ } ++ ++ return ret; ++} ++ ++static void isys_notifier_cleanup(struct ipu6_isys *isys) ++{ ++ v4l2_async_nf_unregister(&isys->notifier); ++ v4l2_async_nf_cleanup(&isys->notifier); ++} ++ ++static int isys_register_devices(struct ipu6_isys *isys) ++{ ++ struct device *dev = &isys->adev->auxdev.dev; ++ struct pci_dev *pdev = isys->adev->isp->pdev; ++ int ret; ++ ++ isys->media_dev.dev = dev; ++ media_device_pci_init(&isys->media_dev, ++ pdev, IPU6_MEDIA_DEV_MODEL_NAME); ++ ++ strscpy(isys->v4l2_dev.name, isys->media_dev.model, ++ sizeof(isys->v4l2_dev.name)); ++ ++ ret = media_device_register(&isys->media_dev); ++ if (ret < 0) ++ goto out_media_device_unregister; ++ ++ isys->v4l2_dev.mdev = &isys->media_dev; ++ isys->v4l2_dev.ctrl_handler = NULL; ++ ++ ret = v4l2_device_register(dev->parent, &isys->v4l2_dev); ++ if (ret < 0) ++ goto out_media_device_unregister; ++ ++ ret = isys_register_video_devices(isys); ++ if (ret) ++ goto out_v4l2_device_unregister; ++ ++ ret = isys_csi2_register_subdevices(isys); ++ if (ret) ++ goto out_isys_unregister_video_device; ++ ++ ret = isys_csi2_create_media_links(isys); ++ if (ret) ++ goto out_isys_unregister_subdevices; ++ ++ ret = isys_notifier_init(isys); ++ if (ret) ++ goto out_isys_unregister_subdevices; ++ ++ return 0; ++ ++out_isys_unregister_subdevices: ++ isys_csi2_unregister_subdevices(isys); ++ ++out_isys_unregister_video_device: ++ isys_unregister_video_devices(isys); ++ ++out_v4l2_device_unregister: ++ v4l2_device_unregister(&isys->v4l2_dev); ++ ++out_media_device_unregister: ++ media_device_unregister(&isys->media_dev); ++ media_device_cleanup(&isys->media_dev); ++ ++ dev_err(dev, "failed to register isys devices\n"); ++ ++ return ret; ++} ++ ++static void isys_unregister_devices(struct ipu6_isys *isys) ++{ ++ isys_unregister_video_devices(isys); ++ isys_csi2_unregister_subdevices(isys); ++ v4l2_device_unregister(&isys->v4l2_dev); ++ media_device_unregister(&isys->media_dev); ++ media_device_cleanup(&isys->media_dev); ++} ++ ++static int isys_runtime_pm_resume(struct device *dev) ++{ ++ struct ipu6_bus_device *adev = to_ipu6_bus_device(dev); ++ struct ipu6_isys *isys = ipu6_bus_get_drvdata(adev); ++ struct ipu6_device *isp = adev->isp; ++ unsigned long flags; ++ int ret; ++ ++ if (!isys) ++ return 0; ++ ++ ret = ipu6_mmu_hw_init(adev->mmu); ++ if (ret) ++ return ret; ++ ++ cpu_latency_qos_update_request(&isys->pm_qos, ISYS_PM_QOS_VALUE); ++ ++ ret = ipu6_buttress_start_tsc_sync(isp); ++ if (ret) ++ return ret; ++ ++ spin_lock_irqsave(&isys->power_lock, flags); ++ isys->power = 1; ++ spin_unlock_irqrestore(&isys->power_lock, flags); ++ ++ isys_setup_hw(isys); ++ ++ set_iwake_ltrdid(isys, 0, 0, LTR_ISYS_ON); ++ ++ return 0; ++} ++ ++static int isys_runtime_pm_suspend(struct device *dev) ++{ ++ struct ipu6_bus_device *adev = to_ipu6_bus_device(dev); ++ struct ipu6_isys *isys; ++ unsigned long flags; ++ ++ isys = dev_get_drvdata(dev); ++ if (!isys) ++ return 0; ++ ++ spin_lock_irqsave(&isys->power_lock, flags); ++ isys->power = 0; ++ spin_unlock_irqrestore(&isys->power_lock, flags); ++ ++ mutex_lock(&isys->mutex); ++ isys->need_reset = false; ++ mutex_unlock(&isys->mutex); ++ ++ isys->phy_termcal_val = 0; ++ cpu_latency_qos_update_request(&isys->pm_qos, PM_QOS_DEFAULT_VALUE); ++ ++ set_iwake_ltrdid(isys, 0, 0, LTR_ISYS_OFF); ++ ++ ipu6_mmu_hw_cleanup(adev->mmu); ++ ++ return 0; ++} ++ ++static int isys_suspend(struct device *dev) ++{ ++ struct ipu6_isys *isys = dev_get_drvdata(dev); ++ ++ /* If stream is open, refuse to suspend */ ++ if (isys->stream_opened) ++ return -EBUSY; ++ ++ return 0; ++} ++ ++static int isys_resume(struct device *dev) ++{ ++ return 0; ++} ++ ++static const struct dev_pm_ops isys_pm_ops = { ++ .runtime_suspend = isys_runtime_pm_suspend, ++ .runtime_resume = isys_runtime_pm_resume, ++ .suspend = isys_suspend, ++ .resume = isys_resume, ++}; ++ ++static void isys_remove(struct auxiliary_device *auxdev) ++{ ++ struct ipu6_bus_device *adev = auxdev_to_adev(auxdev); ++ struct ipu6_isys *isys = dev_get_drvdata(&auxdev->dev); ++ struct ipu6_device *isp = adev->isp; ++ struct isys_fw_msgs *fwmsg, *safe; ++ unsigned int i; ++ ++ list_for_each_entry_safe(fwmsg, safe, &isys->framebuflist, head) ++ dma_free_attrs(&auxdev->dev, sizeof(struct isys_fw_msgs), ++ fwmsg, fwmsg->dma_addr, 0); ++ ++ list_for_each_entry_safe(fwmsg, safe, &isys->framebuflist_fw, head) ++ dma_free_attrs(&auxdev->dev, sizeof(struct isys_fw_msgs), ++ fwmsg, fwmsg->dma_addr, 0); ++ ++ isys_unregister_devices(isys); ++ isys_notifier_cleanup(isys); ++ ++ cpu_latency_qos_remove_request(&isys->pm_qos); ++ ++ if (!isp->secure_mode) { ++ ipu6_cpd_free_pkg_dir(adev); ++ ipu6_buttress_unmap_fw_image(adev, &adev->fw_sgt); ++ release_firmware(adev->fw); ++ } ++ ++ for (i = 0; i < IPU6_ISYS_MAX_STREAMS; i++) ++ mutex_destroy(&isys->streams[i].mutex); ++ ++ isys_iwake_watermark_cleanup(isys); ++ mutex_destroy(&isys->stream_mutex); ++ mutex_destroy(&isys->mutex); ++} ++ ++static int alloc_fw_msg_bufs(struct ipu6_isys *isys, int amount) ++{ ++ struct device *dev = &isys->adev->auxdev.dev; ++ struct isys_fw_msgs *addr; ++ dma_addr_t dma_addr; ++ unsigned long flags; ++ unsigned int i; ++ ++ for (i = 0; i < amount; i++) { ++ addr = dma_alloc_attrs(dev, sizeof(struct isys_fw_msgs), ++ &dma_addr, GFP_KERNEL, 0); ++ if (!addr) ++ break; ++ addr->dma_addr = dma_addr; ++ ++ spin_lock_irqsave(&isys->listlock, flags); ++ list_add(&addr->head, &isys->framebuflist); ++ spin_unlock_irqrestore(&isys->listlock, flags); ++ } ++ ++ if (i == amount) ++ return 0; ++ ++ spin_lock_irqsave(&isys->listlock, flags); ++ while (!list_empty(&isys->framebuflist)) { ++ addr = list_first_entry(&isys->framebuflist, ++ struct isys_fw_msgs, head); ++ list_del(&addr->head); ++ spin_unlock_irqrestore(&isys->listlock, flags); ++ dma_free_attrs(dev, sizeof(struct isys_fw_msgs), addr, ++ addr->dma_addr, 0); ++ spin_lock_irqsave(&isys->listlock, flags); ++ } ++ spin_unlock_irqrestore(&isys->listlock, flags); ++ ++ return -ENOMEM; ++} ++ ++struct isys_fw_msgs *ipu6_get_fw_msg_buf(struct ipu6_isys_stream *stream) ++{ ++ struct ipu6_isys *isys = stream->isys; ++ struct device *dev = &isys->adev->auxdev.dev; ++ struct isys_fw_msgs *msg; ++ unsigned long flags; ++ int ret; ++ ++ spin_lock_irqsave(&isys->listlock, flags); ++ if (list_empty(&isys->framebuflist)) { ++ spin_unlock_irqrestore(&isys->listlock, flags); ++ dev_dbg(dev, "Frame list empty\n"); ++ ++ ret = alloc_fw_msg_bufs(isys, 5); ++ if (ret < 0) ++ return NULL; ++ ++ spin_lock_irqsave(&isys->listlock, flags); ++ if (list_empty(&isys->framebuflist)) { ++ spin_unlock_irqrestore(&isys->listlock, flags); ++ dev_err(dev, "Frame list empty\n"); ++ return NULL; ++ } ++ } ++ msg = list_last_entry(&isys->framebuflist, struct isys_fw_msgs, head); ++ list_move(&msg->head, &isys->framebuflist_fw); ++ spin_unlock_irqrestore(&isys->listlock, flags); ++ memset(&msg->fw_msg, 0, sizeof(msg->fw_msg)); ++ ++ return msg; ++} ++ ++void ipu6_cleanup_fw_msg_bufs(struct ipu6_isys *isys) ++{ ++ struct isys_fw_msgs *fwmsg, *fwmsg0; ++ unsigned long flags; ++ ++ spin_lock_irqsave(&isys->listlock, flags); ++ list_for_each_entry_safe(fwmsg, fwmsg0, &isys->framebuflist_fw, head) ++ list_move(&fwmsg->head, &isys->framebuflist); ++ spin_unlock_irqrestore(&isys->listlock, flags); ++} ++ ++void ipu6_put_fw_msg_buf(struct ipu6_isys *isys, u64 data) ++{ ++ struct isys_fw_msgs *msg; ++ unsigned long flags; ++ u64 *ptr = (u64 *)data; ++ ++ if (!ptr) ++ return; ++ ++ spin_lock_irqsave(&isys->listlock, flags); ++ msg = container_of(ptr, struct isys_fw_msgs, fw_msg.dummy); ++ list_move(&msg->head, &isys->framebuflist); ++ spin_unlock_irqrestore(&isys->listlock, flags); ++} ++ ++static int isys_probe(struct auxiliary_device *auxdev, ++ const struct auxiliary_device_id *auxdev_id) ++{ ++ struct ipu6_bus_device *adev = auxdev_to_adev(auxdev); ++ struct ipu6_device *isp = adev->isp; ++ const struct firmware *fw; ++ struct ipu6_isys *isys; ++ unsigned int i; ++ int ret; ++ ++ if (!isp->bus_ready_to_probe) ++ return -EPROBE_DEFER; ++ ++ isys = devm_kzalloc(&auxdev->dev, sizeof(*isys), GFP_KERNEL); ++ if (!isys) ++ return -ENOMEM; ++ ++ ret = ipu6_mmu_hw_init(adev->mmu); ++ if (ret) ++ return ret; ++ ++ adev->auxdrv_data = ++ (const struct ipu6_auxdrv_data *)auxdev_id->driver_data; ++ adev->auxdrv = to_auxiliary_drv(auxdev->dev.driver); ++ isys->adev = adev; ++ isys->pdata = adev->pdata; ++ ++ /* initial sensor type */ ++ isys->sensor_type = isys->pdata->ipdata->sensor_type_start; ++ ++ spin_lock_init(&isys->streams_lock); ++ spin_lock_init(&isys->power_lock); ++ isys->power = 0; ++ isys->phy_termcal_val = 0; ++ ++ mutex_init(&isys->mutex); ++ mutex_init(&isys->stream_mutex); ++ ++ spin_lock_init(&isys->listlock); ++ INIT_LIST_HEAD(&isys->framebuflist); ++ INIT_LIST_HEAD(&isys->framebuflist_fw); ++ ++ isys->line_align = IPU6_ISYS_2600_MEM_LINE_ALIGN; ++ isys->icache_prefetch = 0; ++ ++ dev_set_drvdata(&auxdev->dev, isys); ++ ++ isys_stream_init(isys); ++ ++ if (!isp->secure_mode) { ++ fw = isp->cpd_fw; ++ ret = ipu6_buttress_map_fw_image(adev, fw, &adev->fw_sgt); ++ if (ret) ++ goto release_firmware; ++ ++ ret = ipu6_cpd_create_pkg_dir(adev, isp->cpd_fw->data); ++ if (ret) ++ goto remove_shared_buffer; ++ } ++ ++ cpu_latency_qos_add_request(&isys->pm_qos, PM_QOS_DEFAULT_VALUE); ++ ++ ret = alloc_fw_msg_bufs(isys, 20); ++ if (ret < 0) ++ goto out_remove_pkg_dir_shared_buffer; ++ ++ isys_iwake_watermark_init(isys); ++ ++ if (is_ipu6se(adev->isp->hw_ver)) ++ isys->phy_set_power = ipu6_isys_jsl_phy_set_power; ++ else if (is_ipu6ep_mtl(adev->isp->hw_ver)) ++ isys->phy_set_power = ipu6_isys_dwc_phy_set_power; ++ else ++ isys->phy_set_power = ipu6_isys_mcd_phy_set_power; ++ ++ ret = isys_register_devices(isys); ++ if (ret) ++ goto out_remove_pkg_dir_shared_buffer; ++ ++ ipu6_mmu_hw_cleanup(adev->mmu); ++ ++ return 0; ++ ++out_remove_pkg_dir_shared_buffer: ++ if (!isp->secure_mode) ++ ipu6_cpd_free_pkg_dir(adev); ++remove_shared_buffer: ++ if (!isp->secure_mode) ++ ipu6_buttress_unmap_fw_image(adev, &adev->fw_sgt); ++release_firmware: ++ if (!isp->secure_mode) ++ release_firmware(adev->fw); ++ ++ for (i = 0; i < IPU6_ISYS_MAX_STREAMS; i++) ++ mutex_destroy(&isys->streams[i].mutex); ++ ++ mutex_destroy(&isys->mutex); ++ mutex_destroy(&isys->stream_mutex); ++ ++ ipu6_mmu_hw_cleanup(adev->mmu); ++ ++ return ret; ++} ++ ++struct fwmsg { ++ int type; ++ char *msg; ++ bool valid_ts; ++}; ++ ++static const struct fwmsg fw_msg[] = { ++ {IPU6_FW_ISYS_RESP_TYPE_STREAM_OPEN_DONE, "STREAM_OPEN_DONE", 0}, ++ {IPU6_FW_ISYS_RESP_TYPE_STREAM_CLOSE_ACK, "STREAM_CLOSE_ACK", 0}, ++ {IPU6_FW_ISYS_RESP_TYPE_STREAM_START_ACK, "STREAM_START_ACK", 0}, ++ {IPU6_FW_ISYS_RESP_TYPE_STREAM_START_AND_CAPTURE_ACK, ++ "STREAM_START_AND_CAPTURE_ACK", 0}, ++ {IPU6_FW_ISYS_RESP_TYPE_STREAM_STOP_ACK, "STREAM_STOP_ACK", 0}, ++ {IPU6_FW_ISYS_RESP_TYPE_STREAM_FLUSH_ACK, "STREAM_FLUSH_ACK", 0}, ++ {IPU6_FW_ISYS_RESP_TYPE_PIN_DATA_READY, "PIN_DATA_READY", 1}, ++ {IPU6_FW_ISYS_RESP_TYPE_STREAM_CAPTURE_ACK, "STREAM_CAPTURE_ACK", 0}, ++ {IPU6_FW_ISYS_RESP_TYPE_STREAM_START_AND_CAPTURE_DONE, ++ "STREAM_START_AND_CAPTURE_DONE", 1}, ++ {IPU6_FW_ISYS_RESP_TYPE_STREAM_CAPTURE_DONE, "STREAM_CAPTURE_DONE", 1}, ++ {IPU6_FW_ISYS_RESP_TYPE_FRAME_SOF, "FRAME_SOF", 1}, ++ {IPU6_FW_ISYS_RESP_TYPE_FRAME_EOF, "FRAME_EOF", 1}, ++ {IPU6_FW_ISYS_RESP_TYPE_STATS_DATA_READY, "STATS_READY", 1}, ++ {-1, "UNKNOWN MESSAGE", 0} ++}; ++ ++static u32 resp_type_to_index(int type) ++{ ++ unsigned int i; ++ ++ for (i = 0; i < ARRAY_SIZE(fw_msg); i++) ++ if (fw_msg[i].type == type) ++ return i; ++ ++ return ARRAY_SIZE(fw_msg) - 1; ++} ++ ++static int isys_isr_one(struct ipu6_bus_device *adev) ++{ ++ struct ipu6_isys *isys = ipu6_bus_get_drvdata(adev); ++ struct ipu6_fw_isys_resp_info_abi *resp; ++ struct ipu6_isys_stream *stream; ++ struct ipu6_isys_csi2 *csi2 = NULL; ++ u32 index; ++ u64 ts; ++ ++ if (!isys->fwcom) ++ return 1; ++ ++ resp = ipu6_fw_isys_get_resp(isys->fwcom, IPU6_BASE_MSG_RECV_QUEUES); ++ if (!resp) ++ return 1; ++ ++ ts = (u64)resp->timestamp[1] << 32 | resp->timestamp[0]; ++ ++ index = resp_type_to_index(resp->type); ++ dev_dbg(&adev->auxdev.dev, ++ "FW resp %02d %s, stream %u, ts 0x%16.16llx, pin %d\n", ++ resp->type, fw_msg[index].msg, resp->stream_handle, ++ fw_msg[index].valid_ts ? ts : 0, resp->pin_id); ++ ++ if (resp->error_info.error == IPU6_FW_ISYS_ERROR_STREAM_IN_SUSPENSION) ++ /* Suspension is kind of special case: not enough buffers */ ++ dev_dbg(&adev->auxdev.dev, ++ "FW error resp SUSPENSION, details %d\n", ++ resp->error_info.error_details); ++ else if (resp->error_info.error) ++ dev_dbg(&adev->auxdev.dev, ++ "FW error resp error %d, details %d\n", ++ resp->error_info.error, resp->error_info.error_details); ++ ++ if (resp->stream_handle >= IPU6_ISYS_MAX_STREAMS) { ++ dev_err(&adev->auxdev.dev, "bad stream handle %u\n", ++ resp->stream_handle); ++ goto leave; ++ } ++ ++ stream = ipu6_isys_query_stream_by_handle(isys, resp->stream_handle); ++ if (!stream) { ++ dev_err(&adev->auxdev.dev, "stream of stream_handle %u is unused\n", ++ resp->stream_handle); ++ goto leave; ++ } ++ stream->error = resp->error_info.error; ++ ++ csi2 = ipu6_isys_subdev_to_csi2(stream->asd); ++ ++ switch (resp->type) { ++ case IPU6_FW_ISYS_RESP_TYPE_STREAM_OPEN_DONE: ++ complete(&stream->stream_open_completion); ++ break; ++ case IPU6_FW_ISYS_RESP_TYPE_STREAM_CLOSE_ACK: ++ complete(&stream->stream_close_completion); ++ break; ++ case IPU6_FW_ISYS_RESP_TYPE_STREAM_START_ACK: ++ complete(&stream->stream_start_completion); ++ break; ++ case IPU6_FW_ISYS_RESP_TYPE_STREAM_START_AND_CAPTURE_ACK: ++ complete(&stream->stream_start_completion); ++ break; ++ case IPU6_FW_ISYS_RESP_TYPE_STREAM_STOP_ACK: ++ complete(&stream->stream_stop_completion); ++ break; ++ case IPU6_FW_ISYS_RESP_TYPE_STREAM_FLUSH_ACK: ++ complete(&stream->stream_stop_completion); ++ break; ++ case IPU6_FW_ISYS_RESP_TYPE_PIN_DATA_READY: ++ /* ++ * firmware only release the capture msg until software ++ * get pin_data_ready event ++ */ ++ ipu6_put_fw_msg_buf(ipu6_bus_get_drvdata(adev), resp->buf_id); ++ if (resp->pin_id < IPU6_ISYS_OUTPUT_PINS && ++ stream->output_pins[resp->pin_id].pin_ready) ++ stream->output_pins[resp->pin_id].pin_ready(stream, ++ resp); ++ else ++ dev_warn(&adev->auxdev.dev, ++ "%d:No data pin ready handler for pin id %d\n", ++ resp->stream_handle, resp->pin_id); ++ if (csi2) ++ ipu6_isys_csi2_error(csi2); ++ ++ break; ++ case IPU6_FW_ISYS_RESP_TYPE_STREAM_CAPTURE_ACK: ++ break; ++ case IPU6_FW_ISYS_RESP_TYPE_STREAM_START_AND_CAPTURE_DONE: ++ case IPU6_FW_ISYS_RESP_TYPE_STREAM_CAPTURE_DONE: ++ break; ++ case IPU6_FW_ISYS_RESP_TYPE_FRAME_SOF: ++ ++ ipu6_isys_csi2_sof_event_by_stream(stream); ++ stream->seq[stream->seq_index].sequence = ++ atomic_read(&stream->sequence) - 1; ++ stream->seq[stream->seq_index].timestamp = ts; ++ dev_dbg(&adev->auxdev.dev, ++ "sof: handle %d: (index %u), timestamp 0x%16.16llx\n", ++ resp->stream_handle, ++ stream->seq[stream->seq_index].sequence, ts); ++ stream->seq_index = (stream->seq_index + 1) ++ % IPU6_ISYS_MAX_PARALLEL_SOF; ++ break; ++ case IPU6_FW_ISYS_RESP_TYPE_FRAME_EOF: ++ ipu6_isys_csi2_eof_event_by_stream(stream); ++ dev_dbg(&adev->auxdev.dev, ++ "eof: handle %d: (index %u), timestamp 0x%16.16llx\n", ++ resp->stream_handle, ++ stream->seq[stream->seq_index].sequence, ts); ++ break; ++ case IPU6_FW_ISYS_RESP_TYPE_STATS_DATA_READY: ++ break; ++ default: ++ dev_err(&adev->auxdev.dev, "%d:unknown response type %u\n", ++ resp->stream_handle, resp->type); ++ break; ++ } ++ ++ ipu6_isys_put_stream(stream); ++leave: ++ ipu6_fw_isys_put_resp(isys->fwcom, IPU6_BASE_MSG_RECV_QUEUES); ++ return 0; ++} ++ ++static const struct ipu6_auxdrv_data ipu6_isys_auxdrv_data = { ++ .isr = isys_isr, ++ .isr_threaded = NULL, ++ .wake_isr_thread = false, ++}; ++ ++static const struct auxiliary_device_id ipu6_isys_id_table[] = { ++ { ++ .name = "intel_ipu6.isys", ++ .driver_data = (kernel_ulong_t)&ipu6_isys_auxdrv_data, ++ }, ++ { } ++}; ++MODULE_DEVICE_TABLE(auxiliary, ipu6_isys_id_table); ++ ++static struct auxiliary_driver isys_driver = { ++ .name = IPU6_ISYS_NAME, ++ .probe = isys_probe, ++ .remove = isys_remove, ++ .id_table = ipu6_isys_id_table, ++ .driver = { ++ .pm = &isys_pm_ops, ++ }, ++}; ++ ++module_auxiliary_driver(isys_driver); ++ ++MODULE_AUTHOR("Sakari Ailus <sakari.ailus@linux.intel.com>"); ++MODULE_AUTHOR("Tianshu Qiu <tian.shu.qiu@intel.com>"); ++MODULE_AUTHOR("Bingbu Cao <bingbu.cao@intel.com>"); ++MODULE_AUTHOR("Yunliang Ding <yunliang.ding@intel.com>"); ++MODULE_AUTHOR("Hongju Wang <hongju.wang@intel.com>"); ++MODULE_LICENSE("GPL"); ++MODULE_DESCRIPTION("Intel IPU6 input system driver"); ++MODULE_IMPORT_NS(INTEL_IPU6); ++MODULE_IMPORT_NS(INTEL_IPU_BRIDGE); +diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys.h b/drivers/media/pci/intel/ipu6/ipu6-isys.h +new file mode 100644 +index 000000000000..cf7a90bfedc9 +--- /dev/null ++++ b/drivers/media/pci/intel/ipu6/ipu6-isys.h +@@ -0,0 +1,207 @@ ++/* SPDX-License-Identifier: GPL-2.0-only */ ++/* Copyright (C) 2013 - 2023 Intel Corporation */ ++ ++#ifndef IPU6_ISYS_H ++#define IPU6_ISYS_H ++ ++#include <linux/irqreturn.h> ++#include <linux/list.h> ++#include <linux/mutex.h> ++#include <linux/pm_qos.h> ++#include <linux/spinlock_types.h> ++#include <linux/types.h> ++ ++#include <media/media-device.h> ++#include <media/v4l2-async.h> ++#include <media/v4l2-device.h> ++ ++#include "ipu6.h" ++#include "ipu6-fw-isys.h" ++#include "ipu6-isys-csi2.h" ++#include "ipu6-isys-video.h" ++ ++struct ipu6_bus_device; ++ ++#define IPU6_ISYS_ENTITY_PREFIX "Intel IPU6" ++/* FW support max 16 streams */ ++#define IPU6_ISYS_MAX_STREAMS 16 ++#define ISYS_UNISPART_IRQS (IPU6_ISYS_UNISPART_IRQ_SW | \ ++ IPU6_ISYS_UNISPART_IRQ_CSI0 | \ ++ IPU6_ISYS_UNISPART_IRQ_CSI1) ++ ++#define IPU6_ISYS_2600_MEM_LINE_ALIGN 64 ++ ++/* ++ * Current message queue configuration. These must be big enough ++ * so that they never gets full. Queues are located in system memory ++ */ ++#define IPU6_ISYS_SIZE_RECV_QUEUE 40 ++#define IPU6_ISYS_SIZE_SEND_QUEUE 40 ++#define IPU6_ISYS_SIZE_PROXY_RECV_QUEUE 5 ++#define IPU6_ISYS_SIZE_PROXY_SEND_QUEUE 5 ++#define IPU6_ISYS_NUM_RECV_QUEUE 1 ++ ++#define IPU6_ISYS_MIN_WIDTH 1U ++#define IPU6_ISYS_MIN_HEIGHT 1U ++#define IPU6_ISYS_MAX_WIDTH 4672U ++#define IPU6_ISYS_MAX_HEIGHT 3416U ++ ++/* the threshold granularity is 2KB on IPU6 */ ++#define IPU6_SRAM_GRANULARITY_SHIFT 11 ++#define IPU6_SRAM_GRANULARITY_SIZE 2048 ++/* the threshold granularity is 1KB on IPU6SE */ ++#define IPU6SE_SRAM_GRANULARITY_SHIFT 10 ++#define IPU6SE_SRAM_GRANULARITY_SIZE 1024 ++/* IS pixel buffer is 256KB, MaxSRAMSize is 200KB on IPU6 */ ++#define IPU6_MAX_SRAM_SIZE (200 << 10) ++/* IS pixel buffer is 128KB, MaxSRAMSize is 96KB on IPU6SE */ ++#define IPU6SE_MAX_SRAM_SIZE (96 << 10) ++ ++#define IPU6EP_LTR_VALUE 200 ++#define IPU6EP_MIN_MEMOPEN_TH 0x4 ++#define IPU6EP_MTL_LTR_VALUE 1023 ++#define IPU6EP_MTL_MIN_MEMOPEN_TH 0xc ++ ++struct ltr_did { ++ union { ++ u32 value; ++ struct { ++ u8 val0; ++ u8 val1; ++ u8 val2; ++ u8 val3; ++ } bits; ++ } lut_ltr; ++ union { ++ u32 value; ++ struct { ++ u8 th0; ++ u8 th1; ++ u8 th2; ++ u8 th3; ++ } bits; ++ } lut_fill_time; ++}; ++ ++struct isys_iwake_watermark { ++ bool iwake_enabled; ++ bool force_iwake_disable; ++ u32 iwake_threshold; ++ u64 isys_pixelbuffer_datarate; ++ struct ltr_did ltrdid; ++ struct mutex mutex; /* protect whole struct */ ++ struct ipu6_isys *isys; ++ struct list_head video_list; ++}; ++ ++struct ipu6_isys_csi2_config { ++ u32 nlanes; ++ u32 port; ++}; ++ ++struct sensor_async_sd { ++ struct v4l2_async_connection asc; ++ struct ipu6_isys_csi2_config csi2; ++}; ++ ++/* ++ * struct ipu6_isys ++ * ++ * @media_dev: Media device ++ * @v4l2_dev: V4L2 device ++ * @adev: ISYS bus device ++ * @power: Is ISYS powered on or not? ++ * @isr_bits: Which bits does the ISR handle? ++ * @power_lock: Serialise access to power (power state in general) ++ * @csi2_rx_ctrl_cached: cached shared value between all CSI2 receivers ++ * @streams_lock: serialise access to streams ++ * @streams: streams per firmware stream ID ++ * @fwcom: fw communication layer private pointer ++ * or optional external library private pointer ++ * @line_align: line alignment in memory ++ * @phy_termcal_val: the termination calibration value, only used for DWC PHY ++ * @need_reset: Isys requires d0i0->i3 transition ++ * @ref_count: total number of callers fw open ++ * @mutex: serialise access isys video open/release related operations ++ * @stream_mutex: serialise stream start and stop, queueing requests ++ * @pdata: platform data pointer ++ * @csi2: CSI-2 receivers ++ */ ++struct ipu6_isys { ++ struct media_device media_dev; ++ struct v4l2_device v4l2_dev; ++ struct ipu6_bus_device *adev; ++ ++ int power; ++ spinlock_t power_lock; ++ u32 isr_csi2_bits; ++ u32 csi2_rx_ctrl_cached; ++ spinlock_t streams_lock; ++ struct ipu6_isys_stream streams[IPU6_ISYS_MAX_STREAMS]; ++ int streams_ref_count[IPU6_ISYS_MAX_STREAMS]; ++ void *fwcom; ++ unsigned int line_align; ++ u32 phy_termcal_val; ++ bool need_reset; ++ bool icache_prefetch; ++ bool csi2_cse_ipc_not_supported; ++ unsigned int ref_count; ++ unsigned int stream_opened; ++ unsigned int sensor_type; ++ ++ struct mutex mutex; ++ struct mutex stream_mutex; ++ ++ struct ipu6_isys_pdata *pdata; ++ ++ int (*phy_set_power)(struct ipu6_isys *isys, ++ struct ipu6_isys_csi2_config *cfg, ++ const struct ipu6_isys_csi2_timing *timing, ++ bool on); ++ ++ struct ipu6_isys_csi2 *csi2; ++ struct ipu6_isys_video av[NR_OF_VIDEO_DEVICE]; ++ ++ struct pm_qos_request pm_qos; ++ spinlock_t listlock; /* Protect framebuflist */ ++ struct list_head framebuflist; ++ struct list_head framebuflist_fw; ++ struct v4l2_async_notifier notifier; ++ struct isys_iwake_watermark iwake_watermark; ++}; ++ ++struct isys_fw_msgs { ++ union { ++ u64 dummy; ++ struct ipu6_fw_isys_frame_buff_set_abi frame; ++ struct ipu6_fw_isys_stream_cfg_data_abi stream; ++ } fw_msg; ++ struct list_head head; ++ dma_addr_t dma_addr; ++}; ++ ++struct isys_fw_msgs *ipu6_get_fw_msg_buf(struct ipu6_isys_stream *stream); ++void ipu6_put_fw_msg_buf(struct ipu6_isys *isys, u64 data); ++void ipu6_cleanup_fw_msg_bufs(struct ipu6_isys *isys); ++ ++extern const struct v4l2_ioctl_ops ipu6_isys_ioctl_ops; ++ ++void isys_setup_hw(struct ipu6_isys *isys); ++irqreturn_t isys_isr(struct ipu6_bus_device *adev); ++void update_watermark_setting(struct ipu6_isys *isys); ++ ++int ipu6_isys_mcd_phy_set_power(struct ipu6_isys *isys, ++ struct ipu6_isys_csi2_config *cfg, ++ const struct ipu6_isys_csi2_timing *timing, ++ bool on); ++ ++int ipu6_isys_dwc_phy_set_power(struct ipu6_isys *isys, ++ struct ipu6_isys_csi2_config *cfg, ++ const struct ipu6_isys_csi2_timing *timing, ++ bool on); ++ ++int ipu6_isys_jsl_phy_set_power(struct ipu6_isys *isys, ++ struct ipu6_isys_csi2_config *cfg, ++ const struct ipu6_isys_csi2_timing *timing, ++ bool on); ++#endif /* IPU6_ISYS_H */ +-- +2.43.0 + +From 32d07a2e879187ea87b90256ac32a41080e3b8bc Mon Sep 17 00:00:00 2001 +From: Bingbu Cao <bingbu.cao@intel.com> +Date: Thu, 11 Jan 2024 14:55:25 +0800 +Subject: [PATCH 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 <bingbu.cao@intel.com> +Link: https://lore.kernel.org/r/20240111065531.2418836-12-bingbu.cao@intel.com +Signed-off-by: Hans de Goede <hdegoede@redhat.com> +--- + .../media/pci/intel/ipu6/ipu6-isys-queue.c | 825 +++++++++++ + .../media/pci/intel/ipu6/ipu6-isys-queue.h | 76 + + .../media/pci/intel/ipu6/ipu6-isys-video.c | 1253 +++++++++++++++++ + .../media/pci/intel/ipu6/ipu6-isys-video.h | 136 ++ + 4 files changed, 2290 insertions(+) + create mode 100644 drivers/media/pci/intel/ipu6/ipu6-isys-queue.c + create mode 100644 drivers/media/pci/intel/ipu6/ipu6-isys-queue.h + create mode 100644 drivers/media/pci/intel/ipu6/ipu6-isys-video.c + create mode 100644 drivers/media/pci/intel/ipu6/ipu6-isys-video.h + +diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys-queue.c b/drivers/media/pci/intel/ipu6/ipu6-isys-queue.c +new file mode 100644 +index 000000000000..735d2d642d87 +--- /dev/null ++++ b/drivers/media/pci/intel/ipu6/ipu6-isys-queue.c +@@ -0,0 +1,825 @@ ++// SPDX-License-Identifier: GPL-2.0-only ++/* ++ * Copyright (C) 2013 - 2023 Intel Corporation ++ */ ++#include <linux/atomic.h> ++#include <linux/bug.h> ++#include <linux/device.h> ++#include <linux/list.h> ++#include <linux/lockdep.h> ++#include <linux/mutex.h> ++#include <linux/spinlock.h> ++#include <linux/types.h> ++ ++#include <media/media-entity.h> ++#include <media/v4l2-subdev.h> ++#include <media/videobuf2-dma-contig.h> ++#include <media/videobuf2-v4l2.h> ++ ++#include "ipu6-bus.h" ++#include "ipu6-fw-isys.h" ++#include "ipu6-isys.h" ++#include "ipu6-isys-video.h" ++ ++static int queue_setup(struct vb2_queue *q, unsigned int *num_buffers, ++ unsigned int *num_planes, unsigned int sizes[], ++ struct device *alloc_devs[]) ++{ ++ struct ipu6_isys_queue *aq = vb2_queue_to_isys_queue(q); ++ struct ipu6_isys_video *av = ipu6_isys_queue_to_video(aq); ++ struct device *dev = &av->isys->adev->auxdev.dev; ++ bool use_fmt = false; ++ unsigned int i; ++ u32 size; ++ ++ /* num_planes == 0: we're being called through VIDIOC_REQBUFS */ ++ if (!*num_planes) { ++ use_fmt = true; ++ *num_planes = av->mpix.num_planes; ++ } ++ ++ for (i = 0; i < *num_planes; i++) { ++ size = av->mpix.plane_fmt[i].sizeimage; ++ if (use_fmt) { ++ sizes[i] = size; ++ } else if (sizes[i] < size) { ++ dev_err(dev, "%s: queue setup: plane %d size %u < %u\n", ++ av->vdev.name, i, sizes[i], size); ++ return -EINVAL; ++ } ++ ++ alloc_devs[i] = aq->dev; ++ } ++ ++ return 0; ++} ++ ++static int ipu6_isys_buf_prepare(struct vb2_buffer *vb) ++{ ++ struct ipu6_isys_queue *aq = vb2_queue_to_isys_queue(vb->vb2_queue); ++ struct ipu6_isys_video *av = ipu6_isys_queue_to_video(aq); ++ struct device *dev = &av->isys->adev->auxdev.dev; ++ ++ dev_dbg(dev, "buffer: %s: configured size %u, buffer size %lu\n", ++ av->vdev.name, av->mpix.plane_fmt[0].sizeimage, ++ vb2_plane_size(vb, 0)); ++ ++ if (av->mpix.plane_fmt[0].sizeimage > vb2_plane_size(vb, 0)) ++ return -EINVAL; ++ ++ vb2_set_plane_payload(vb, 0, av->mpix.plane_fmt[0].bytesperline * ++ av->mpix.height); ++ vb->planes[0].data_offset = 0; ++ ++ return 0; ++} ++ ++/* ++ * Queue a buffer list back to incoming or active queues. The buffers ++ * are removed from the buffer list. ++ */ ++void ipu6_isys_buffer_list_queue(struct ipu6_isys_buffer_list *bl, ++ unsigned long op_flags, ++ enum vb2_buffer_state state) ++{ ++ struct ipu6_isys_buffer *ib, *ib_safe; ++ unsigned long flags; ++ bool first = true; ++ ++ if (!bl) ++ return; ++ ++ WARN_ON_ONCE(!bl->nbufs); ++ WARN_ON_ONCE(op_flags & IPU6_ISYS_BUFFER_LIST_FL_ACTIVE && ++ op_flags & IPU6_ISYS_BUFFER_LIST_FL_INCOMING); ++ ++ list_for_each_entry_safe(ib, ib_safe, &bl->head, head) { ++ struct ipu6_isys_video *av; ++ struct vb2_buffer *vb = ipu6_isys_buffer_to_vb2_buffer(ib); ++ struct ipu6_isys_queue *aq = ++ vb2_queue_to_isys_queue(vb->vb2_queue); ++ struct device *dev; ++ ++ av = ipu6_isys_queue_to_video(aq); ++ dev = &av->isys->adev->auxdev.dev; ++ spin_lock_irqsave(&aq->lock, flags); ++ list_del(&ib->head); ++ if (op_flags & IPU6_ISYS_BUFFER_LIST_FL_ACTIVE) ++ list_add(&ib->head, &aq->active); ++ else if (op_flags & IPU6_ISYS_BUFFER_LIST_FL_INCOMING) ++ list_add_tail(&ib->head, &aq->incoming); ++ spin_unlock_irqrestore(&aq->lock, flags); ++ ++ if (op_flags & IPU6_ISYS_BUFFER_LIST_FL_SET_STATE) ++ vb2_buffer_done(vb, state); ++ ++ if (first) { ++ dev_dbg(dev, ++ "queue buf list %p flags %lx, s %d, %d bufs\n", ++ bl, op_flags, state, bl->nbufs); ++ first = false; ++ } ++ ++ bl->nbufs--; ++ } ++ ++ WARN_ON(bl->nbufs); ++} ++ ++/* ++ * flush_firmware_streamon_fail() - Flush in cases where requests may ++ * have been queued to firmware and the *firmware streamon fails for a ++ * reason or another. ++ */ ++static void flush_firmware_streamon_fail(struct ipu6_isys_stream *stream) ++{ ++ struct device *dev = &stream->isys->adev->auxdev.dev; ++ struct ipu6_isys_queue *aq; ++ unsigned long flags; ++ ++ lockdep_assert_held(&stream->mutex); ++ ++ list_for_each_entry(aq, &stream->queues, node) { ++ struct ipu6_isys_video *av = ipu6_isys_queue_to_video(aq); ++ struct ipu6_isys_buffer *ib, *ib_safe; ++ ++ spin_lock_irqsave(&aq->lock, flags); ++ list_for_each_entry_safe(ib, ib_safe, &aq->active, head) { ++ struct vb2_buffer *vb = ++ ipu6_isys_buffer_to_vb2_buffer(ib); ++ ++ list_del(&ib->head); ++ if (av->streaming) { ++ dev_dbg(dev, ++ "%s: queue buffer %u back to incoming\n", ++ av->vdev.name, vb->index); ++ /* Queue already streaming, return to driver. */ ++ list_add(&ib->head, &aq->incoming); ++ continue; ++ } ++ /* Queue not yet streaming, return to user. */ ++ dev_dbg(dev, "%s: return %u back to videobuf2\n", ++ av->vdev.name, vb->index); ++ vb2_buffer_done(ipu6_isys_buffer_to_vb2_buffer(ib), ++ VB2_BUF_STATE_QUEUED); ++ } ++ spin_unlock_irqrestore(&aq->lock, flags); ++ } ++} ++ ++/* ++ * Attempt obtaining a buffer list from the incoming queues, a list of buffers ++ * that contains one entry from each video buffer queue. If a buffer can't be ++ * obtained from every queue, the buffers are returned back to the queue. ++ */ ++static int buffer_list_get(struct ipu6_isys_stream *stream, ++ struct ipu6_isys_buffer_list *bl) ++{ ++ struct device *dev = &stream->isys->adev->auxdev.dev; ++ struct ipu6_isys_queue *aq; ++ unsigned long flags; ++ unsigned long buf_flag = IPU6_ISYS_BUFFER_LIST_FL_INCOMING; ++ ++ bl->nbufs = 0; ++ INIT_LIST_HEAD(&bl->head); ++ ++ list_for_each_entry(aq, &stream->queues, node) { ++ struct ipu6_isys_buffer *ib; ++ ++ spin_lock_irqsave(&aq->lock, flags); ++ if (list_empty(&aq->incoming)) { ++ spin_unlock_irqrestore(&aq->lock, flags); ++ if (!list_empty(&bl->head)) ++ ipu6_isys_buffer_list_queue(bl, buf_flag, 0); ++ return -ENODATA; ++ } ++ ++ ib = list_last_entry(&aq->incoming, ++ struct ipu6_isys_buffer, head); ++ ++ dev_dbg(dev, "buffer: %s: buffer %u\n", ++ ipu6_isys_queue_to_video(aq)->vdev.name, ++ ipu6_isys_buffer_to_vb2_buffer(ib)->index); ++ list_del(&ib->head); ++ list_add(&ib->head, &bl->head); ++ spin_unlock_irqrestore(&aq->lock, flags); ++ ++ bl->nbufs++; ++ } ++ ++ dev_dbg(dev, "get buffer list %p, %u buffers\n", bl, bl->nbufs); ++ ++ return 0; ++} ++ ++static void ++ipu6_isys_buf_to_fw_frame_buf_pin(struct vb2_buffer *vb, ++ struct ipu6_fw_isys_frame_buff_set_abi *set) ++{ ++ struct ipu6_isys_queue *aq = vb2_queue_to_isys_queue(vb->vb2_queue); ++ ++ set->output_pins[aq->fw_output].addr = ++ vb2_dma_contig_plane_dma_addr(vb, 0); ++ set->output_pins[aq->fw_output].out_buf_id = vb->index + 1; ++} ++ ++/* ++ * Convert a buffer list to a isys fw ABI framebuffer set. The ++ * buffer list is not modified. ++ */ ++#define IPU6_ISYS_FRAME_NUM_THRESHOLD (30) ++void ++ipu6_isys_buf_to_fw_frame_buf(struct ipu6_fw_isys_frame_buff_set_abi *set, ++ struct ipu6_isys_stream *stream, ++ struct ipu6_isys_buffer_list *bl) ++{ ++ struct ipu6_isys_buffer *ib; ++ ++ WARN_ON(!bl->nbufs); ++ ++ set->send_irq_sof = 1; ++ set->send_resp_sof = 1; ++ set->send_irq_eof = 0; ++ set->send_resp_eof = 0; ++ ++ if (stream->streaming) ++ set->send_irq_capture_ack = 0; ++ else ++ set->send_irq_capture_ack = 1; ++ set->send_irq_capture_done = 0; ++ ++ set->send_resp_capture_ack = 1; ++ set->send_resp_capture_done = 1; ++ if (atomic_read(&stream->sequence) >= IPU6_ISYS_FRAME_NUM_THRESHOLD) { ++ set->send_resp_capture_ack = 0; ++ set->send_resp_capture_done = 0; ++ } ++ ++ list_for_each_entry(ib, &bl->head, head) { ++ struct vb2_buffer *vb = ipu6_isys_buffer_to_vb2_buffer(ib); ++ ++ ipu6_isys_buf_to_fw_frame_buf_pin(vb, set); ++ } ++} ++ ++/* Start streaming for real. The buffer list must be available. */ ++static int ipu6_isys_stream_start(struct ipu6_isys_video *av, ++ struct ipu6_isys_buffer_list *bl, bool error) ++{ ++ struct ipu6_isys_stream *stream = av->stream; ++ struct device *dev = &stream->isys->adev->auxdev.dev; ++ struct ipu6_isys_buffer_list __bl; ++ int ret; ++ ++ mutex_lock(&stream->isys->stream_mutex); ++ ret = ipu6_isys_video_set_streaming(av, 1, bl); ++ mutex_unlock(&stream->isys->stream_mutex); ++ if (ret) ++ goto out_requeue; ++ ++ stream->streaming = 1; ++ ++ bl = &__bl; ++ ++ do { ++ struct ipu6_fw_isys_frame_buff_set_abi *buf = NULL; ++ struct isys_fw_msgs *msg; ++ u16 send_type = IPU6_FW_ISYS_SEND_TYPE_STREAM_CAPTURE; ++ ++ ret = buffer_list_get(stream, bl); ++ if (ret < 0) ++ break; ++ ++ msg = ipu6_get_fw_msg_buf(stream); ++ if (!msg) ++ return -ENOMEM; ++ ++ buf = &msg->fw_msg.frame; ++ ipu6_isys_buf_to_fw_frame_buf(buf, stream, bl); ++ ipu6_fw_isys_dump_frame_buff_set(dev, buf, ++ stream->nr_output_pins); ++ ipu6_isys_buffer_list_queue(bl, IPU6_ISYS_BUFFER_LIST_FL_ACTIVE, ++ 0); ++ ret = ipu6_fw_isys_complex_cmd(stream->isys, ++ stream->stream_handle, buf, ++ msg->dma_addr, sizeof(*buf), ++ send_type); ++ } while (!WARN_ON(ret)); ++ ++ return 0; ++ ++out_requeue: ++ if (bl && bl->nbufs) ++ ipu6_isys_buffer_list_queue(bl, ++ (IPU6_ISYS_BUFFER_LIST_FL_INCOMING | ++ error) ? ++ IPU6_ISYS_BUFFER_LIST_FL_SET_STATE : ++ 0, error ? VB2_BUF_STATE_ERROR : ++ VB2_BUF_STATE_QUEUED); ++ flush_firmware_streamon_fail(stream); ++ ++ return ret; ++} ++ ++static void buf_queue(struct vb2_buffer *vb) ++{ ++ struct ipu6_isys_queue *aq = vb2_queue_to_isys_queue(vb->vb2_queue); ++ struct ipu6_isys_video *av = ipu6_isys_queue_to_video(aq); ++ struct vb2_v4l2_buffer *vvb = to_vb2_v4l2_buffer(vb); ++ struct ipu6_isys_video_buffer *ivb = ++ vb2_buffer_to_ipu6_isys_video_buffer(vvb); ++ struct ipu6_isys_buffer *ib = &ivb->ib; ++ struct device *dev = &av->isys->adev->auxdev.dev; ++ struct media_pipeline *media_pipe = ++ media_entity_pipeline(&av->vdev.entity); ++ struct ipu6_fw_isys_frame_buff_set_abi *buf = NULL; ++ struct ipu6_isys_stream *stream = av->stream; ++ struct ipu6_isys_buffer_list bl; ++ struct isys_fw_msgs *msg; ++ unsigned long flags; ++ dma_addr_t dma; ++ unsigned int i; ++ int ret; ++ ++ dev_dbg(dev, "queue buffer %u for %s\n", vb->index, av->vdev.name); ++ ++ for (i = 0; i < vb->num_planes; i++) { ++ dma = vb2_dma_contig_plane_dma_addr(vb, i); ++ dev_dbg(dev, "iova: plane %u iova %pad\n", i, &dma); ++ } ++ ++ spin_lock_irqsave(&aq->lock, flags); ++ list_add(&ib->head, &aq->incoming); ++ spin_unlock_irqrestore(&aq->lock, flags); ++ ++ if (!media_pipe || !vb->vb2_queue->start_streaming_called) { ++ dev_dbg(dev, "media pipeline is not ready for %s\n", ++ av->vdev.name); ++ return; ++ } ++ ++ mutex_lock(&stream->mutex); ++ ++ if (stream->nr_streaming != stream->nr_queues) { ++ dev_dbg(dev, "not streaming yet, adding to incoming\n"); ++ goto out; ++ } ++ ++ /* ++ * We just put one buffer to the incoming list of this queue ++ * (above). Let's see whether all queues in the pipeline would ++ * have a buffer. ++ */ ++ ret = buffer_list_get(stream, &bl); ++ if (ret < 0) { ++ dev_warn(dev, "No buffers available\n"); ++ goto out; ++ } ++ ++ msg = ipu6_get_fw_msg_buf(stream); ++ if (!msg) { ++ ret = -ENOMEM; ++ goto out; ++ } ++ ++ buf = &msg->fw_msg.frame; ++ ipu6_isys_buf_to_fw_frame_buf(buf, stream, &bl); ++ ipu6_fw_isys_dump_frame_buff_set(dev, buf, stream->nr_output_pins); ++ ++ if (!stream->streaming) { ++ ret = ipu6_isys_stream_start(av, &bl, true); ++ if (ret) ++ dev_err(dev, "stream start failed.\n"); ++ goto out; ++ } ++ ++ /* ++ * We must queue the buffers in the buffer list to the ++ * appropriate video buffer queues BEFORE passing them to the ++ * firmware since we could get a buffer event back before we ++ * have queued them ourselves to the active queue. ++ */ ++ ipu6_isys_buffer_list_queue(&bl, IPU6_ISYS_BUFFER_LIST_FL_ACTIVE, 0); ++ ++ ret = ipu6_fw_isys_complex_cmd(stream->isys, stream->stream_handle, ++ buf, msg->dma_addr, sizeof(*buf), ++ IPU6_FW_ISYS_SEND_TYPE_STREAM_CAPTURE); ++ if (ret < 0) ++ dev_err(dev, "send stream capture failed\n"); ++ ++out: ++ mutex_unlock(&stream->mutex); ++} ++ ++static int ipu6_isys_link_fmt_validate(struct ipu6_isys_queue *aq) ++{ ++ struct v4l2_mbus_framefmt format; ++ struct ipu6_isys_video *av = ipu6_isys_queue_to_video(aq); ++ struct device *dev = &av->isys->adev->auxdev.dev; ++ struct media_pad *remote_pad = ++ media_pad_remote_pad_first(av->vdev.entity.pads); ++ struct v4l2_subdev *sd; ++ u32 r_stream; ++ int ret; ++ ++ if (!remote_pad) ++ return -ENOTCONN; ++ ++ sd = media_entity_to_v4l2_subdev(remote_pad->entity); ++ r_stream = ipu6_isys_get_src_stream_by_src_pad(sd, remote_pad->index); ++ ++ ret = ipu6_isys_get_stream_pad_fmt(sd, remote_pad->index, r_stream, ++ &format); ++ ++ if (ret) { ++ dev_dbg(dev, "failed to get %s: pad %d, stream:%d format\n", ++ sd->entity.name, remote_pad->index, r_stream); ++ return ret; ++ } ++ ++ if (format.width != av->mpix.width || ++ format.height != av->mpix.height) { ++ dev_dbg(dev, "wrong width or height %ux%u (%ux%u expected)\n", ++ av->mpix.width, av->mpix.height, ++ format.width, format.height); ++ return -EINVAL; ++ } ++ ++ if (format.field != av->mpix.field) { ++ dev_dbg(dev, "wrong field value 0x%8.8x (0x%8.8x expected)\n", ++ av->mpix.field, format.field); ++ return -EINVAL; ++ } ++ ++ if (format.code != av->pfmt->code) { ++ dev_dbg(dev, "wrong mbus code 0x%8.8x (0x%8.8x expected)\n", ++ av->pfmt->code, format.code); ++ return -EINVAL; ++ } ++ ++ return 0; ++} ++ ++static void return_buffers(struct ipu6_isys_queue *aq, ++ enum vb2_buffer_state state) ++{ ++ struct ipu6_isys_video *av = ipu6_isys_queue_to_video(aq); ++ struct ipu6_isys_buffer *ib; ++ bool need_reset = false; ++ unsigned long flags; ++ ++ spin_lock_irqsave(&aq->lock, flags); ++ while (!list_empty(&aq->incoming)) { ++ struct vb2_buffer *vb; ++ ++ ib = list_first_entry(&aq->incoming, struct ipu6_isys_buffer, ++ head); ++ vb = ipu6_isys_buffer_to_vb2_buffer(ib); ++ list_del(&ib->head); ++ spin_unlock_irqrestore(&aq->lock, flags); ++ ++ vb2_buffer_done(vb, state); ++ ++ spin_lock_irqsave(&aq->lock, flags); ++ } ++ ++ /* ++ * Something went wrong (FW crash / HW hang / not all buffers ++ * returned from isys) if there are still buffers queued in active ++ * queue. We have to clean up places a bit. ++ */ ++ while (!list_empty(&aq->active)) { ++ struct vb2_buffer *vb; ++ ++ ib = list_first_entry(&aq->active, struct ipu6_isys_buffer, ++ head); ++ vb = ipu6_isys_buffer_to_vb2_buffer(ib); ++ ++ list_del(&ib->head); ++ spin_unlock_irqrestore(&aq->lock, flags); ++ ++ vb2_buffer_done(vb, state); ++ ++ spin_lock_irqsave(&aq->lock, flags); ++ need_reset = true; ++ } ++ ++ spin_unlock_irqrestore(&aq->lock, flags); ++ ++ if (need_reset) { ++ mutex_lock(&av->isys->mutex); ++ av->isys->need_reset = true; ++ mutex_unlock(&av->isys->mutex); ++ } ++} ++ ++static void ipu6_isys_stream_cleanup(struct ipu6_isys_video *av) ++{ ++ video_device_pipeline_stop(&av->vdev); ++ ipu6_isys_put_stream(av->stream); ++ av->stream = NULL; ++} ++ ++static int start_streaming(struct vb2_queue *q, unsigned int count) ++{ ++ struct ipu6_isys_queue *aq = vb2_queue_to_isys_queue(q); ++ struct ipu6_isys_video *av = ipu6_isys_queue_to_video(aq); ++ struct device *dev = &av->isys->adev->auxdev.dev; ++ struct ipu6_isys_buffer_list __bl, *bl = NULL; ++ struct ipu6_isys_stream *stream; ++ struct media_entity *source_entity = NULL; ++ int nr_queues, ret; ++ ++ dev_dbg(dev, "stream: %s: width %u, height %u, css pixelformat %u\n", ++ av->vdev.name, av->mpix.width, av->mpix.height, ++ av->pfmt->css_pixelformat); ++ ++ ret = ipu6_isys_setup_video(av, &source_entity, &nr_queues); ++ if (ret < 0) { ++ dev_err(dev, "failed to setup video\n"); ++ goto out_return_buffers; ++ } ++ ++ ret = ipu6_isys_link_fmt_validate(aq); ++ if (ret) { ++ dev_err(dev, ++ "%s: link format validation failed (%d)\n", ++ av->vdev.name, ret); ++ goto out_pipeline_stop; ++ } ++ ++ ret = ipu6_isys_fw_open(av->isys); ++ if (ret) ++ goto out_pipeline_stop; ++ ++ stream = av->stream; ++ mutex_lock(&stream->mutex); ++ if (!stream->nr_streaming) { ++ ret = ipu6_isys_video_prepare_stream(av, source_entity, ++ nr_queues); ++ if (ret) ++ goto out_fw_close; ++ } ++ ++ stream->nr_streaming++; ++ dev_dbg(dev, "queue %u of %u\n", stream->nr_streaming, ++ stream->nr_queues); ++ ++ list_add(&aq->node, &stream->queues); ++ ipu6_isys_set_csi2_streams_status(av, true); ++ ipu6_isys_configure_stream_watermark(av, true); ++ ipu6_isys_update_stream_watermark(av, true); ++ ++ if (stream->nr_streaming != stream->nr_queues) ++ goto out; ++ ++ bl = &__bl; ++ ret = buffer_list_get(stream, bl); ++ if (ret < 0) { ++ dev_dbg(dev, ++ "no buffer available, postponing streamon\n"); ++ goto out; ++ } ++ ++ ret = ipu6_isys_stream_start(av, bl, false); ++ if (ret) ++ goto out_stream_start; ++ ++out: ++ mutex_unlock(&stream->mutex); ++ ++ return 0; ++ ++out_stream_start: ++ list_del(&aq->node); ++ stream->nr_streaming--; ++ ++out_fw_close: ++ mutex_unlock(&stream->mutex); ++ ipu6_isys_fw_close(av->isys); ++ ++out_pipeline_stop: ++ ipu6_isys_stream_cleanup(av); ++ ++out_return_buffers: ++ return_buffers(aq, VB2_BUF_STATE_QUEUED); ++ ++ return ret; ++} ++ ++static void stop_streaming(struct vb2_queue *q) ++{ ++ struct ipu6_isys_queue *aq = vb2_queue_to_isys_queue(q); ++ struct ipu6_isys_video *av = ipu6_isys_queue_to_video(aq); ++ struct ipu6_isys_stream *stream = av->stream; ++ ++ ipu6_isys_set_csi2_streams_status(av, false); ++ ++ mutex_lock(&stream->mutex); ++ ++ ipu6_isys_update_stream_watermark(av, false); ++ ++ mutex_lock(&av->isys->stream_mutex); ++ if (stream->nr_streaming == stream->nr_queues && stream->streaming) ++ ipu6_isys_video_set_streaming(av, 0, NULL); ++ mutex_unlock(&av->isys->stream_mutex); ++ ++ stream->nr_streaming--; ++ list_del(&aq->node); ++ stream->streaming = 0; ++ mutex_unlock(&stream->mutex); ++ ++ ipu6_isys_stream_cleanup(av); ++ ++ return_buffers(aq, VB2_BUF_STATE_ERROR); ++ ++ ipu6_isys_fw_close(av->isys); ++} ++ ++static unsigned int ++get_sof_sequence_by_timestamp(struct ipu6_isys_stream *stream, ++ struct ipu6_fw_isys_resp_info_abi *info) ++{ ++ u64 time = (u64)info->timestamp[1] << 32 | info->timestamp[0]; ++ struct ipu6_isys *isys = stream->isys; ++ struct device *dev = &isys->adev->auxdev.dev; ++ unsigned int i; ++ ++ /* ++ * The timestamp is invalid as no TSC in some FPGA platform, ++ * so get the sequence from pipeline directly in this case. ++ */ ++ if (time == 0) ++ return atomic_read(&stream->sequence) - 1; ++ ++ for (i = 0; i < IPU6_ISYS_MAX_PARALLEL_SOF; i++) ++ if (time == stream->seq[i].timestamp) { ++ dev_dbg(dev, "sof: using seq nr %u for ts %llu\n", ++ stream->seq[i].sequence, time); ++ return stream->seq[i].sequence; ++ } ++ ++ for (i = 0; i < IPU6_ISYS_MAX_PARALLEL_SOF; i++) ++ dev_dbg(dev, "sof: sequence %u, timestamp value %llu\n", ++ stream->seq[i].sequence, stream->seq[i].timestamp); ++ ++ return 0; ++} ++ ++static u64 get_sof_ns_delta(struct ipu6_isys_video *av, ++ struct ipu6_fw_isys_resp_info_abi *info) ++{ ++ struct ipu6_bus_device *adev = av->isys->adev; ++ struct ipu6_device *isp = adev->isp; ++ u64 delta, tsc_now; ++ ++ ipu6_buttress_tsc_read(isp, &tsc_now); ++ if (!tsc_now) ++ return 0; ++ ++ delta = tsc_now - ((u64)info->timestamp[1] << 32 | info->timestamp[0]); ++ ++ return ipu6_buttress_tsc_ticks_to_ns(delta, isp); ++} ++ ++void ipu6_isys_buf_calc_sequence_time(struct ipu6_isys_buffer *ib, ++ struct ipu6_fw_isys_resp_info_abi *info) ++{ ++ struct vb2_buffer *vb = ipu6_isys_buffer_to_vb2_buffer(ib); ++ struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb); ++ struct ipu6_isys_queue *aq = vb2_queue_to_isys_queue(vb->vb2_queue); ++ struct ipu6_isys_video *av = ipu6_isys_queue_to_video(aq); ++ struct device *dev = &av->isys->adev->auxdev.dev; ++ struct ipu6_isys_stream *stream = av->stream; ++ u64 ns; ++ u32 sequence; ++ ++ ns = ktime_get_ns() - get_sof_ns_delta(av, info); ++ sequence = get_sof_sequence_by_timestamp(stream, info); ++ ++ vbuf->vb2_buf.timestamp = ns; ++ vbuf->sequence = sequence; ++ ++ dev_dbg(dev, "buf: %s: buffer done, CPU-timestamp:%lld, sequence:%d\n", ++ av->vdev.name, ktime_get_ns(), sequence); ++ dev_dbg(dev, "index:%d, vbuf timestamp:%lld\n", vb->index, ++ vbuf->vb2_buf.timestamp); ++} ++ ++void ipu6_isys_queue_buf_done(struct ipu6_isys_buffer *ib) ++{ ++ struct vb2_buffer *vb = ipu6_isys_buffer_to_vb2_buffer(ib); ++ ++ if (atomic_read(&ib->str2mmio_flag)) { ++ vb2_buffer_done(vb, VB2_BUF_STATE_ERROR); ++ /* ++ * Operation on buffer is ended with error and will be reported ++ * to the userspace when it is de-queued ++ */ ++ atomic_set(&ib->str2mmio_flag, 0); ++ } else { ++ vb2_buffer_done(vb, VB2_BUF_STATE_DONE); ++ } ++} ++ ++void ipu6_isys_queue_buf_ready(struct ipu6_isys_stream *stream, ++ struct ipu6_fw_isys_resp_info_abi *info) ++{ ++ struct ipu6_isys_queue *aq = stream->output_pins[info->pin_id].aq; ++ struct ipu6_isys *isys = stream->isys; ++ struct device *dev = &isys->adev->auxdev.dev; ++ struct ipu6_isys_buffer *ib; ++ struct vb2_buffer *vb; ++ unsigned long flags; ++ bool first = true; ++ struct vb2_v4l2_buffer *buf; ++ ++ spin_lock_irqsave(&aq->lock, flags); ++ if (list_empty(&aq->active)) { ++ spin_unlock_irqrestore(&aq->lock, flags); ++ dev_err(dev, "active queue empty\n"); ++ return; ++ } ++ ++ list_for_each_entry_reverse(ib, &aq->active, head) { ++ dma_addr_t addr; ++ ++ vb = ipu6_isys_buffer_to_vb2_buffer(ib); ++ addr = vb2_dma_contig_plane_dma_addr(vb, 0); ++ ++ if (info->pin.addr != addr) { ++ if (first) ++ dev_err(dev, "Unexpected buffer address %pad\n", ++ &addr); ++ first = false; ++ continue; ++ } ++ ++ if (info->error_info.error == ++ IPU6_FW_ISYS_ERROR_HW_REPORTED_STR2MMIO) { ++ /* ++ * Check for error message: ++ * 'IPU6_FW_ISYS_ERROR_HW_REPORTED_STR2MMIO' ++ */ ++ atomic_set(&ib->str2mmio_flag, 1); ++ } ++ dev_dbg(dev, "buffer: found buffer %pad\n", &addr); ++ ++ buf = to_vb2_v4l2_buffer(vb); ++ buf->field = V4L2_FIELD_NONE; ++ ++ list_del(&ib->head); ++ spin_unlock_irqrestore(&aq->lock, flags); ++ ++ ipu6_isys_buf_calc_sequence_time(ib, info); ++ ++ ipu6_isys_queue_buf_done(ib); ++ ++ return; ++ } ++ ++ dev_err(dev, "Failed to find a matching video buffer"); ++ ++ spin_unlock_irqrestore(&aq->lock, flags); ++} ++ ++static const struct vb2_ops ipu6_isys_queue_ops = { ++ .queue_setup = queue_setup, ++ .wait_prepare = vb2_ops_wait_prepare, ++ .wait_finish = vb2_ops_wait_finish, ++ .buf_prepare = ipu6_isys_buf_prepare, ++ .start_streaming = start_streaming, ++ .stop_streaming = stop_streaming, ++ .buf_queue = buf_queue, ++}; ++ ++int ipu6_isys_queue_init(struct ipu6_isys_queue *aq) ++{ ++ struct ipu6_isys *isys = ipu6_isys_queue_to_video(aq)->isys; ++ struct ipu6_isys_video *av = ipu6_isys_queue_to_video(aq); ++ int ret; ++ ++ /* no support for userptr */ ++ if (!aq->vbq.io_modes) ++ aq->vbq.io_modes = VB2_MMAP | VB2_DMABUF; ++ ++ aq->vbq.drv_priv = aq; ++ aq->vbq.ops = &ipu6_isys_queue_ops; ++ aq->vbq.lock = &av->mutex; ++ aq->vbq.mem_ops = &vb2_dma_contig_memops; ++ aq->vbq.type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; ++ aq->vbq.timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC; ++ ++ ret = vb2_queue_init(&aq->vbq); ++ if (ret) ++ return ret; ++ ++ aq->dev = &isys->adev->auxdev.dev; ++ aq->vbq.dev = &isys->adev->auxdev.dev; ++ spin_lock_init(&aq->lock); ++ INIT_LIST_HEAD(&aq->active); ++ INIT_LIST_HEAD(&aq->incoming); ++ ++ return 0; ++} ++ +diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys-queue.h b/drivers/media/pci/intel/ipu6/ipu6-isys-queue.h +new file mode 100644 +index 000000000000..9fb454577bb5 +--- /dev/null ++++ b/drivers/media/pci/intel/ipu6/ipu6-isys-queue.h +@@ -0,0 +1,76 @@ ++/* SPDX-License-Identifier: GPL-2.0-only */ ++/* Copyright (C) 2013 - 2023 Intel Corporation */ ++ ++#ifndef IPU6_ISYS_QUEUE_H ++#define IPU6_ISYS_QUEUE_H ++ ++#include <linux/container_of.h> ++#include <linux/atomic.h> ++#include <linux/device.h> ++#include <linux/list.h> ++#include <linux/spinlock_types.h> ++ ++#include <media/videobuf2-v4l2.h> ++ ++#include "ipu6-fw-isys.h" ++#include "ipu6-isys-video.h" ++ ++struct ipu6_isys_queue { ++ struct vb2_queue vbq; ++ struct list_head node; ++ struct device *dev; ++ /* ++ * @lock: serialise access to queued and pre_streamon_queued ++ */ ++ spinlock_t lock; ++ struct list_head active; ++ struct list_head incoming; ++ unsigned int fw_output; ++}; ++ ++struct ipu6_isys_buffer { ++ struct list_head head; ++ atomic_t str2mmio_flag; ++}; ++ ++struct ipu6_isys_video_buffer { ++ struct vb2_v4l2_buffer vb_v4l2; ++ struct ipu6_isys_buffer ib; ++}; ++ ++#define IPU6_ISYS_BUFFER_LIST_FL_INCOMING BIT(0) ++#define IPU6_ISYS_BUFFER_LIST_FL_ACTIVE BIT(1) ++#define IPU6_ISYS_BUFFER_LIST_FL_SET_STATE BIT(2) ++ ++struct ipu6_isys_buffer_list { ++ struct list_head head; ++ unsigned int nbufs; ++}; ++ ++#define vb2_queue_to_isys_queue(__vb2) \ ++ container_of(__vb2, struct ipu6_isys_queue, vbq) ++ ++#define ipu6_isys_to_isys_video_buffer(__ib) \ ++ container_of(__ib, struct ipu6_isys_video_buffer, ib) ++ ++#define vb2_buffer_to_ipu6_isys_video_buffer(__vvb) \ ++ container_of(__vvb, struct ipu6_isys_video_buffer, vb_v4l2) ++ ++#define ipu6_isys_buffer_to_vb2_buffer(__ib) \ ++ (&ipu6_isys_to_isys_video_buffer(__ib)->vb_v4l2.vb2_buf) ++ ++void ipu6_isys_buffer_list_queue(struct ipu6_isys_buffer_list *bl, ++ unsigned long op_flags, ++ enum vb2_buffer_state state); ++void ++ipu6_isys_buf_to_fw_frame_buf(struct ipu6_fw_isys_frame_buff_set_abi *set, ++ struct ipu6_isys_stream *stream, ++ struct ipu6_isys_buffer_list *bl); ++void ++ipu6_isys_buf_calc_sequence_time(struct ipu6_isys_buffer *ib, ++ struct ipu6_fw_isys_resp_info_abi *info); ++void ipu6_isys_queue_buf_done(struct ipu6_isys_buffer *ib); ++void ipu6_isys_queue_buf_ready(struct ipu6_isys_stream *stream, ++ struct ipu6_fw_isys_resp_info_abi *info); ++int ipu6_isys_queue_init(struct ipu6_isys_queue *aq); ++#endif /* IPU6_ISYS_QUEUE_H */ +diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys-video.c b/drivers/media/pci/intel/ipu6/ipu6-isys-video.c +new file mode 100644 +index 000000000000..847eac26bcd6 +--- /dev/null ++++ b/drivers/media/pci/intel/ipu6/ipu6-isys-video.c +@@ -0,0 +1,1253 @@ ++// SPDX-License-Identifier: GPL-2.0-only ++/* ++ * Copyright (C) 2013 - 2023 Intel Corporation ++ */ ++ ++#include <linux/align.h> ++#include <linux/bits.h> ++#include <linux/bug.h> ++#include <linux/completion.h> ++#include <linux/container_of.h> ++#include <linux/device.h> ++#include <linux/list.h> ++#include <linux/math64.h> ++#include <linux/minmax.h> ++#include <linux/module.h> ++#include <linux/mutex.h> ++#include <linux/pm_runtime.h> ++#include <linux/spinlock.h> ++#include <linux/string.h> ++ ++#include <media/media-entity.h> ++#include <media/v4l2-ctrls.h> ++#include <media/v4l2-dev.h> ++#include <media/v4l2-fh.h> ++#include <media/v4l2-ioctl.h> ++#include <media/v4l2-subdev.h> ++#include <media/videobuf2-v4l2.h> ++ ++#include "ipu6.h" ++#include "ipu6-bus.h" ++#include "ipu6-cpd.h" ++#include "ipu6-fw-isys.h" ++#include "ipu6-isys.h" ++#include "ipu6-isys-csi2.h" ++#include "ipu6-isys-queue.h" ++#include "ipu6-isys-video.h" ++#include "ipu6-platform-regs.h" ++ ++const struct ipu6_isys_pixelformat ipu6_isys_pfmts[] = { ++ {V4L2_PIX_FMT_SBGGR12, 16, 12, MEDIA_BUS_FMT_SBGGR12_1X12, ++ IPU6_FW_ISYS_FRAME_FORMAT_RAW16}, ++ {V4L2_PIX_FMT_SGBRG12, 16, 12, MEDIA_BUS_FMT_SGBRG12_1X12, ++ IPU6_FW_ISYS_FRAME_FORMAT_RAW16}, ++ {V4L2_PIX_FMT_SGRBG12, 16, 12, MEDIA_BUS_FMT_SGRBG12_1X12, ++ IPU6_FW_ISYS_FRAME_FORMAT_RAW16}, ++ {V4L2_PIX_FMT_SRGGB12, 16, 12, MEDIA_BUS_FMT_SRGGB12_1X12, ++ IPU6_FW_ISYS_FRAME_FORMAT_RAW16}, ++ {V4L2_PIX_FMT_SBGGR10, 16, 10, MEDIA_BUS_FMT_SBGGR10_1X10, ++ IPU6_FW_ISYS_FRAME_FORMAT_RAW16}, ++ {V4L2_PIX_FMT_SGBRG10, 16, 10, MEDIA_BUS_FMT_SGBRG10_1X10, ++ IPU6_FW_ISYS_FRAME_FORMAT_RAW16}, ++ {V4L2_PIX_FMT_SGRBG10, 16, 10, MEDIA_BUS_FMT_SGRBG10_1X10, ++ IPU6_FW_ISYS_FRAME_FORMAT_RAW16}, ++ {V4L2_PIX_FMT_SRGGB10, 16, 10, MEDIA_BUS_FMT_SRGGB10_1X10, ++ IPU6_FW_ISYS_FRAME_FORMAT_RAW16}, ++ {V4L2_PIX_FMT_SBGGR8, 8, 8, MEDIA_BUS_FMT_SBGGR8_1X8, ++ IPU6_FW_ISYS_FRAME_FORMAT_RAW8}, ++ {V4L2_PIX_FMT_SGBRG8, 8, 8, MEDIA_BUS_FMT_SGBRG8_1X8, ++ IPU6_FW_ISYS_FRAME_FORMAT_RAW8}, ++ {V4L2_PIX_FMT_SGRBG8, 8, 8, MEDIA_BUS_FMT_SGRBG8_1X8, ++ IPU6_FW_ISYS_FRAME_FORMAT_RAW8}, ++ {V4L2_PIX_FMT_SRGGB8, 8, 8, MEDIA_BUS_FMT_SRGGB8_1X8, ++ IPU6_FW_ISYS_FRAME_FORMAT_RAW8}, ++ {V4L2_PIX_FMT_SBGGR12P, 12, 12, MEDIA_BUS_FMT_SBGGR12_1X12, ++ IPU6_FW_ISYS_FRAME_FORMAT_RAW12}, ++ {V4L2_PIX_FMT_SGBRG12P, 12, 12, MEDIA_BUS_FMT_SGBRG12_1X12, ++ IPU6_FW_ISYS_FRAME_FORMAT_RAW12}, ++ {V4L2_PIX_FMT_SGRBG12P, 12, 12, MEDIA_BUS_FMT_SGRBG12_1X12, ++ IPU6_FW_ISYS_FRAME_FORMAT_RAW12}, ++ {V4L2_PIX_FMT_SRGGB12P, 12, 12, MEDIA_BUS_FMT_SRGGB12_1X12, ++ IPU6_FW_ISYS_FRAME_FORMAT_RAW12}, ++ {V4L2_PIX_FMT_SBGGR10P, 10, 10, MEDIA_BUS_FMT_SBGGR10_1X10, ++ IPU6_FW_ISYS_FRAME_FORMAT_RAW10}, ++ {V4L2_PIX_FMT_SGBRG10P, 10, 10, MEDIA_BUS_FMT_SGBRG10_1X10, ++ IPU6_FW_ISYS_FRAME_FORMAT_RAW10}, ++ {V4L2_PIX_FMT_SGRBG10P, 10, 10, MEDIA_BUS_FMT_SGRBG10_1X10, ++ IPU6_FW_ISYS_FRAME_FORMAT_RAW10}, ++ {V4L2_PIX_FMT_SRGGB10P, 10, 10, MEDIA_BUS_FMT_SRGGB10_1X10, ++ IPU6_FW_ISYS_FRAME_FORMAT_RAW10}, ++ {V4L2_PIX_FMT_UYVY, 16, 16, MEDIA_BUS_FMT_UYVY8_1X16, ++ IPU6_FW_ISYS_FRAME_FORMAT_UYVY}, ++ {V4L2_PIX_FMT_YUYV, 16, 16, MEDIA_BUS_FMT_YUYV8_1X16, ++ IPU6_FW_ISYS_FRAME_FORMAT_YUYV}, ++ {V4L2_PIX_FMT_RGB565, 16, 16, MEDIA_BUS_FMT_RGB565_1X16, ++ IPU6_FW_ISYS_FRAME_FORMAT_RGB565}, ++ {V4L2_PIX_FMT_BGR24, 24, 24, MEDIA_BUS_FMT_RGB888_1X24, ++ IPU6_FW_ISYS_FRAME_FORMAT_RGBA888}, ++}; ++ ++static int video_open(struct file *file) ++{ ++ struct ipu6_isys_video *av = video_drvdata(file); ++ struct ipu6_isys *isys = av->isys; ++ struct ipu6_bus_device *adev = isys->adev; ++ ++ mutex_lock(&isys->mutex); ++ if (isys->need_reset) { ++ mutex_unlock(&isys->mutex); ++ dev_warn(&adev->auxdev.dev, "isys power cycle required\n"); ++ return -EIO; ++ } ++ mutex_unlock(&isys->mutex); ++ ++ return v4l2_fh_open(file); ++} ++ ++static int video_release(struct file *file) ++{ ++ return vb2_fop_release(file); ++} ++ ++static const struct ipu6_isys_pixelformat * ++ipu6_isys_get_pixelformat(u32 pixelformat) ++{ ++ unsigned int i; ++ ++ for (i = 0; i < ARRAY_SIZE(ipu6_isys_pfmts); i++) { ++ const struct ipu6_isys_pixelformat *pfmt = &ipu6_isys_pfmts[i]; ++ ++ if (pfmt->pixelformat == pixelformat) ++ return pfmt; ++ } ++ ++ return &ipu6_isys_pfmts[0]; ++} ++ ++int ipu6_isys_vidioc_querycap(struct file *file, void *fh, ++ struct v4l2_capability *cap) ++{ ++ struct ipu6_isys_video *av = video_drvdata(file); ++ ++ strscpy(cap->driver, IPU6_ISYS_NAME, sizeof(cap->driver)); ++ strscpy(cap->card, av->isys->media_dev.model, sizeof(cap->card)); ++ ++ return 0; ++} ++ ++int ipu6_isys_vidioc_enum_fmt(struct file *file, void *fh, ++ struct v4l2_fmtdesc *f) ++{ ++ unsigned int i, found = 0; ++ ++ if (f->index >= ARRAY_SIZE(ipu6_isys_pfmts)) ++ return -EINVAL; ++ ++ if (!f->mbus_code) { ++ f->flags = 0; ++ f->pixelformat = ipu6_isys_pfmts[f->index].pixelformat; ++ return 0; ++ } ++ ++ for (i = 0; i < ARRAY_SIZE(ipu6_isys_pfmts); i++) { ++ if (f->mbus_code != ipu6_isys_pfmts[i].code) ++ continue; ++ ++ if (f->index == found) { ++ f->flags = 0; ++ f->pixelformat = ipu6_isys_pfmts[i].pixelformat; ++ return 0; ++ } ++ found++; ++ } ++ ++ return -EINVAL; ++} ++ ++static int ipu6_isys_vidioc_enum_framesizes(struct file *file, void *fh, ++ struct v4l2_frmsizeenum *fsize) ++{ ++ if (fsize->index > 0) ++ return -EINVAL; ++ ++ fsize->type = V4L2_FRMSIZE_TYPE_CONTINUOUS; ++ fsize->stepwise.min_width = IPU6_ISYS_MIN_WIDTH; ++ fsize->stepwise.max_width = IPU6_ISYS_MAX_WIDTH; ++ fsize->stepwise.min_height = IPU6_ISYS_MIN_HEIGHT; ++ fsize->stepwise.max_height = IPU6_ISYS_MAX_HEIGHT; ++ fsize->stepwise.step_width = 2; ++ fsize->stepwise.step_height = 2; ++ ++ return 0; ++} ++ ++static int vidioc_g_fmt_vid_cap_mplane(struct file *file, void *fh, ++ struct v4l2_format *fmt) ++{ ++ struct ipu6_isys_video *av = video_drvdata(file); ++ ++ fmt->fmt.pix_mp = av->mpix; ++ ++ return 0; ++} ++ ++static const struct ipu6_isys_pixelformat * ++ipu6_isys_video_try_fmt_vid_mplane(struct ipu6_isys_video *av, ++ struct v4l2_pix_format_mplane *mpix) ++{ ++ const struct ipu6_isys_pixelformat *pfmt = ++ ipu6_isys_get_pixelformat(mpix->pixelformat); ++ ++ mpix->pixelformat = pfmt->pixelformat; ++ mpix->num_planes = 1; ++ ++ mpix->width = clamp(mpix->width, IPU6_ISYS_MIN_WIDTH, ++ IPU6_ISYS_MAX_WIDTH); ++ mpix->height = clamp(mpix->height, IPU6_ISYS_MIN_HEIGHT, ++ IPU6_ISYS_MAX_HEIGHT); ++ ++ if (pfmt->bpp != pfmt->bpp_packed) ++ mpix->plane_fmt[0].bytesperline = ++ mpix->width * DIV_ROUND_UP(pfmt->bpp, BITS_PER_BYTE); ++ else ++ mpix->plane_fmt[0].bytesperline = ++ DIV_ROUND_UP((unsigned int)mpix->width * pfmt->bpp, ++ BITS_PER_BYTE); ++ ++ mpix->plane_fmt[0].bytesperline = ALIGN(mpix->plane_fmt[0].bytesperline, ++ av->isys->line_align); ++ ++ /* ++ * (height + 1) * bytesperline due to a hardware issue: the DMA unit ++ * is a power of two, and a line should be transferred as few units ++ * as possible. The result is that up to line length more data than ++ * the image size may be transferred to memory after the image. ++ * Another limitation is the GDA allocation unit size. For low ++ * resolution it gives a bigger number. Use larger one to avoid ++ * memory corruption. ++ */ ++ mpix->plane_fmt[0].sizeimage = ++ max(mpix->plane_fmt[0].sizeimage, ++ mpix->plane_fmt[0].bytesperline * mpix->height + ++ max(mpix->plane_fmt[0].bytesperline, ++ av->isys->pdata->ipdata->isys_dma_overshoot)); ++ ++ memset(mpix->plane_fmt[0].reserved, 0, ++ sizeof(mpix->plane_fmt[0].reserved)); ++ ++ mpix->field = V4L2_FIELD_NONE; ++ ++ mpix->colorspace = V4L2_COLORSPACE_RAW; ++ mpix->ycbcr_enc = V4L2_YCBCR_ENC_DEFAULT; ++ mpix->quantization = V4L2_QUANTIZATION_DEFAULT; ++ mpix->xfer_func = V4L2_XFER_FUNC_DEFAULT; ++ ++ return pfmt; ++} ++ ++static int vidioc_s_fmt_vid_cap_mplane(struct file *file, void *fh, ++ struct v4l2_format *f) ++{ ++ struct ipu6_isys_video *av = video_drvdata(file); ++ ++ if (av->aq.vbq.streaming) ++ return -EBUSY; ++ ++ av->pfmt = ipu6_isys_video_try_fmt_vid_mplane(av, &f->fmt.pix_mp); ++ av->mpix = f->fmt.pix_mp; ++ ++ return 0; ++} ++ ++static int vidioc_try_fmt_vid_cap_mplane(struct file *file, void *fh, ++ struct v4l2_format *f) ++{ ++ struct ipu6_isys_video *av = video_drvdata(file); ++ ++ ipu6_isys_video_try_fmt_vid_mplane(av, &f->fmt.pix_mp); ++ ++ return 0; ++} ++ ++static int link_validate(struct media_link *link) ++{ ++ struct ipu6_isys_video *av = ++ container_of(link->sink, struct ipu6_isys_video, pad); ++ struct device *dev = &av->isys->adev->auxdev.dev; ++ struct v4l2_subdev_state *s_state; ++ struct v4l2_subdev *s_sd; ++ struct v4l2_mbus_framefmt *s_fmt; ++ struct media_pad *s_pad; ++ u32 s_stream; ++ int ret = -EPIPE; ++ ++ if (!link->source->entity) ++ return ret; ++ ++ s_sd = media_entity_to_v4l2_subdev(link->source->entity); ++ s_state = v4l2_subdev_get_unlocked_active_state(s_sd); ++ if (!s_state) ++ return ret; ++ ++ dev_dbg(dev, "validating link \"%s\":%u -> \"%s\"\n", ++ link->source->entity->name, link->source->index, ++ link->sink->entity->name); ++ ++ s_pad = media_pad_remote_pad_first(&av->pad); ++ s_stream = ipu6_isys_get_src_stream_by_src_pad(s_sd, s_pad->index); ++ ++ v4l2_subdev_lock_state(s_state); ++ ++ s_fmt = v4l2_subdev_state_get_stream_format(s_state, s_pad->index, ++ s_stream); ++ if (!s_fmt) { ++ dev_err(dev, "failed to get source pad format\n"); ++ goto unlock; ++ } ++ ++ if (s_fmt->width != av->mpix.width || ++ s_fmt->height != av->mpix.height || s_fmt->code != av->pfmt->code) { ++ dev_err(dev, "format mismatch %dx%d,%x != %dx%d,%x\n", ++ s_fmt->width, s_fmt->height, s_fmt->code, ++ av->mpix.width, av->mpix.height, av->pfmt->code); ++ goto unlock; ++ } ++ ++ v4l2_subdev_unlock_state(s_state); ++ ++ return 0; ++unlock: ++ v4l2_subdev_unlock_state(s_state); ++ ++ return ret; ++} ++ ++static void get_stream_opened(struct ipu6_isys_video *av) ++{ ++ unsigned long flags; ++ ++ spin_lock_irqsave(&av->isys->streams_lock, flags); ++ av->isys->stream_opened++; ++ spin_unlock_irqrestore(&av->isys->streams_lock, flags); ++} ++ ++static void put_stream_opened(struct ipu6_isys_video *av) ++{ ++ unsigned long flags; ++ ++ spin_lock_irqsave(&av->isys->streams_lock, flags); ++ av->isys->stream_opened--; ++ spin_unlock_irqrestore(&av->isys->streams_lock, flags); ++} ++ ++static int ipu6_isys_fw_pin_cfg(struct ipu6_isys_video *av, ++ struct ipu6_fw_isys_stream_cfg_data_abi *cfg) ++{ ++ struct media_pad *src_pad = media_pad_remote_pad_first(&av->pad); ++ struct v4l2_subdev *sd = media_entity_to_v4l2_subdev(src_pad->entity); ++ struct ipu6_fw_isys_input_pin_info_abi *input_pin; ++ struct ipu6_fw_isys_output_pin_info_abi *output_pin; ++ struct ipu6_isys_stream *stream = av->stream; ++ struct ipu6_isys_queue *aq = &av->aq; ++ struct v4l2_mbus_framefmt fmt; ++ struct v4l2_rect v4l2_crop; ++ struct ipu6_isys *isys = av->isys; ++ struct device *dev = &isys->adev->auxdev.dev; ++ int input_pins = cfg->nof_input_pins++; ++ int output_pins; ++ u32 src_stream; ++ int ret; ++ ++ src_stream = ipu6_isys_get_src_stream_by_src_pad(sd, src_pad->index); ++ ret = ipu6_isys_get_stream_pad_fmt(sd, src_pad->index, src_stream, ++ &fmt); ++ if (ret < 0) { ++ dev_err(dev, "can't get stream format (%d)\n", ret); ++ return ret; ++ } ++ ++ ret = ipu6_isys_get_stream_pad_crop(sd, src_pad->index, src_stream, ++ &v4l2_crop); ++ if (ret < 0) { ++ dev_err(dev, "can't get stream crop (%d)\n", ret); ++ return ret; ++ } ++ ++ input_pin = &cfg->input_pins[input_pins]; ++ input_pin->input_res.width = fmt.width; ++ input_pin->input_res.height = fmt.height; ++ input_pin->dt = av->dt; ++ input_pin->bits_per_pix = av->pfmt->bpp_packed; ++ input_pin->mapped_dt = 0x40; /* invalid mipi data type */ ++ input_pin->mipi_decompression = 0; ++ input_pin->capture_mode = IPU6_FW_ISYS_CAPTURE_MODE_REGULAR; ++ input_pin->mipi_store_mode = av->pfmt->bpp == av->pfmt->bpp_packed ? ++ IPU6_FW_ISYS_MIPI_STORE_MODE_DISCARD_LONG_HEADER : ++ IPU6_FW_ISYS_MIPI_STORE_MODE_NORMAL; ++ input_pin->crop_first_and_last_lines = v4l2_crop.top & 1; ++ ++ output_pins = cfg->nof_output_pins++; ++ aq->fw_output = output_pins; ++ stream->output_pins[output_pins].pin_ready = ipu6_isys_queue_buf_ready; ++ stream->output_pins[output_pins].aq = aq; ++ ++ output_pin = &cfg->output_pins[output_pins]; ++ output_pin->input_pin_id = input_pins; ++ output_pin->output_res.width = av->mpix.width; ++ output_pin->output_res.height = av->mpix.height; ++ ++ output_pin->stride = av->mpix.plane_fmt[0].bytesperline; ++ if (av->pfmt->bpp != av->pfmt->bpp_packed) ++ output_pin->pt = IPU6_FW_ISYS_PIN_TYPE_RAW_SOC; ++ else ++ output_pin->pt = IPU6_FW_ISYS_PIN_TYPE_MIPI; ++ output_pin->ft = av->pfmt->css_pixelformat; ++ output_pin->send_irq = 1; ++ memset(output_pin->ts_offsets, 0, sizeof(output_pin->ts_offsets)); ++ output_pin->s2m_pixel_soc_pixel_remapping = ++ S2M_PIXEL_SOC_PIXEL_REMAPPING_FLAG_NO_REMAPPING; ++ output_pin->csi_be_soc_pixel_remapping = ++ CSI_BE_SOC_PIXEL_REMAPPING_FLAG_NO_REMAPPING; ++ ++ output_pin->snoopable = true; ++ output_pin->error_handling_enable = false; ++ output_pin->sensor_type = isys->sensor_type++; ++ if (isys->sensor_type > isys->pdata->ipdata->sensor_type_end) ++ isys->sensor_type = isys->pdata->ipdata->sensor_type_start; ++ ++ return 0; ++} ++ ++static int start_stream_firmware(struct ipu6_isys_video *av, ++ struct ipu6_isys_buffer_list *bl) ++{ ++ struct ipu6_fw_isys_stream_cfg_data_abi *stream_cfg; ++ struct ipu6_fw_isys_frame_buff_set_abi *buf = NULL; ++ struct ipu6_isys_stream *stream = av->stream; ++ struct device *dev = &av->isys->adev->auxdev.dev; ++ struct isys_fw_msgs *msg = NULL; ++ struct ipu6_isys_queue *aq; ++ int ret, retout, tout; ++ u16 send_type; ++ ++ msg = ipu6_get_fw_msg_buf(stream); ++ if (!msg) ++ return -ENOMEM; ++ ++ stream_cfg = &msg->fw_msg.stream; ++ stream_cfg->src = stream->stream_source; ++ stream_cfg->vc = stream->vc; ++ stream_cfg->isl_use = 0; ++ stream_cfg->sensor_type = IPU6_FW_ISYS_SENSOR_MODE_NORMAL; ++ ++ list_for_each_entry(aq, &stream->queues, node) { ++ struct ipu6_isys_video *__av = ipu6_isys_queue_to_video(aq); ++ ++ ret = ipu6_isys_fw_pin_cfg(__av, stream_cfg); ++ if (ret < 0) { ++ ipu6_put_fw_msg_buf(av->isys, (u64)stream_cfg); ++ return ret; ++ } ++ } ++ ++ ipu6_fw_isys_dump_stream_cfg(dev, stream_cfg); ++ ++ stream->nr_output_pins = stream_cfg->nof_output_pins; ++ ++ reinit_completion(&stream->stream_open_completion); ++ ++ ret = ipu6_fw_isys_complex_cmd(av->isys, stream->stream_handle, ++ stream_cfg, msg->dma_addr, ++ sizeof(*stream_cfg), ++ IPU6_FW_ISYS_SEND_TYPE_STREAM_OPEN); ++ if (ret < 0) { ++ dev_err(dev, "can't open stream (%d)\n", ret); ++ ipu6_put_fw_msg_buf(av->isys, (u64)stream_cfg); ++ return ret; ++ } ++ ++ get_stream_opened(av); ++ ++ tout = wait_for_completion_timeout(&stream->stream_open_completion, ++ IPU6_FW_CALL_TIMEOUT_JIFFIES); ++ ++ ipu6_put_fw_msg_buf(av->isys, (u64)stream_cfg); ++ ++ if (!tout) { ++ dev_err(dev, "stream open time out\n"); ++ ret = -ETIMEDOUT; ++ goto out_put_stream_opened; ++ } ++ if (stream->error) { ++ dev_err(dev, "stream open error: %d\n", stream->error); ++ ret = -EIO; ++ goto out_put_stream_opened; ++ } ++ dev_dbg(dev, "start stream: open complete\n"); ++ ++ if (bl) { ++ msg = ipu6_get_fw_msg_buf(stream); ++ if (!msg) { ++ ret = -ENOMEM; ++ goto out_put_stream_opened; ++ } ++ buf = &msg->fw_msg.frame; ++ ipu6_isys_buf_to_fw_frame_buf(buf, stream, bl); ++ ipu6_isys_buffer_list_queue(bl, ++ IPU6_ISYS_BUFFER_LIST_FL_ACTIVE, 0); ++ } ++ ++ reinit_completion(&stream->stream_start_completion); ++ ++ if (bl) { ++ send_type = IPU6_FW_ISYS_SEND_TYPE_STREAM_START_AND_CAPTURE; ++ ipu6_fw_isys_dump_frame_buff_set(dev, buf, ++ stream_cfg->nof_output_pins); ++ ret = ipu6_fw_isys_complex_cmd(av->isys, stream->stream_handle, ++ buf, msg->dma_addr, ++ sizeof(*buf), send_type); ++ } else { ++ send_type = IPU6_FW_ISYS_SEND_TYPE_STREAM_START; ++ ret = ipu6_fw_isys_simple_cmd(av->isys, stream->stream_handle, ++ send_type); ++ } ++ ++ if (ret < 0) { ++ dev_err(dev, "can't start streaming (%d)\n", ret); ++ goto out_stream_close; ++ } ++ ++ tout = wait_for_completion_timeout(&stream->stream_start_completion, ++ IPU6_FW_CALL_TIMEOUT_JIFFIES); ++ if (!tout) { ++ dev_err(dev, "stream start time out\n"); ++ ret = -ETIMEDOUT; ++ goto out_stream_close; ++ } ++ if (stream->error) { ++ dev_err(dev, "stream start error: %d\n", stream->error); ++ ret = -EIO; ++ goto out_stream_close; ++ } ++ dev_dbg(dev, "start stream: complete\n"); ++ ++ return 0; ++ ++out_stream_close: ++ reinit_completion(&stream->stream_close_completion); ++ ++ retout = ipu6_fw_isys_simple_cmd(av->isys, ++ stream->stream_handle, ++ IPU6_FW_ISYS_SEND_TYPE_STREAM_CLOSE); ++ if (retout < 0) { ++ dev_dbg(dev, "can't close stream (%d)\n", retout); ++ goto out_put_stream_opened; ++ } ++ ++ tout = wait_for_completion_timeout(&stream->stream_close_completion, ++ IPU6_FW_CALL_TIMEOUT_JIFFIES); ++ if (!tout) ++ dev_err(dev, "stream close time out\n"); ++ else if (stream->error) ++ dev_err(dev, "stream close error: %d\n", stream->error); ++ else ++ dev_dbg(dev, "stream close complete\n"); ++ ++out_put_stream_opened: ++ put_stream_opened(av); ++ ++ return ret; ++} ++ ++static void stop_streaming_firmware(struct ipu6_isys_video *av) ++{ ++ struct device *dev = &av->isys->adev->auxdev.dev; ++ struct ipu6_isys_stream *stream = av->stream; ++ int ret, tout; ++ ++ reinit_completion(&stream->stream_stop_completion); ++ ++ ret = ipu6_fw_isys_simple_cmd(av->isys, stream->stream_handle, ++ IPU6_FW_ISYS_SEND_TYPE_STREAM_FLUSH); ++ ++ if (ret < 0) { ++ dev_err(dev, "can't stop stream (%d)\n", ret); ++ return; ++ } ++ ++ tout = wait_for_completion_timeout(&stream->stream_stop_completion, ++ IPU6_FW_CALL_TIMEOUT_JIFFIES); ++ if (!tout) ++ dev_warn(dev, "stream stop time out\n"); ++ else if (stream->error) ++ dev_warn(dev, "stream stop error: %d\n", stream->error); ++ else ++ dev_dbg(dev, "stop stream: complete\n"); ++} ++ ++static void close_streaming_firmware(struct ipu6_isys_video *av) ++{ ++ struct ipu6_isys_stream *stream = av->stream; ++ struct device *dev = &av->isys->adev->auxdev.dev; ++ int ret, tout; ++ ++ reinit_completion(&stream->stream_close_completion); ++ ++ ret = ipu6_fw_isys_simple_cmd(av->isys, stream->stream_handle, ++ IPU6_FW_ISYS_SEND_TYPE_STREAM_CLOSE); ++ if (ret < 0) { ++ dev_err(dev, "can't close stream (%d)\n", ret); ++ return; ++ } ++ ++ tout = wait_for_completion_timeout(&stream->stream_close_completion, ++ IPU6_FW_CALL_TIMEOUT_JIFFIES); ++ if (!tout) ++ dev_warn(dev, "stream close time out\n"); ++ else if (stream->error) ++ dev_warn(dev, "stream close error: %d\n", stream->error); ++ else ++ dev_dbg(dev, "close stream: complete\n"); ++ ++ put_stream_opened(av); ++} ++ ++int ipu6_isys_video_prepare_stream(struct ipu6_isys_video *av, ++ struct media_entity *source_entity, ++ int nr_queues) ++{ ++ struct ipu6_isys_stream *stream = av->stream; ++ struct ipu6_isys_csi2 *csi2; ++ ++ if (WARN_ON(stream->nr_streaming)) ++ return -EINVAL; ++ ++ stream->nr_queues = nr_queues; ++ atomic_set(&stream->sequence, 0); ++ ++ stream->seq_index = 0; ++ memset(stream->seq, 0, sizeof(stream->seq)); ++ ++ if (WARN_ON(!list_empty(&stream->queues))) ++ return -EINVAL; ++ ++ stream->stream_source = stream->asd->source; ++ csi2 = ipu6_isys_subdev_to_csi2(stream->asd); ++ csi2->receiver_errors = 0; ++ stream->source_entity = source_entity; ++ ++ dev_dbg(&av->isys->adev->auxdev.dev, ++ "prepare stream: external entity %s\n", ++ stream->source_entity->name); ++ ++ return 0; ++} ++ ++void ipu6_isys_configure_stream_watermark(struct ipu6_isys_video *av, ++ bool state) ++{ ++ struct ipu6_isys *isys = av->isys; ++ struct ipu6_isys_csi2 *csi2 = NULL; ++ struct isys_iwake_watermark *iwake_watermark = &isys->iwake_watermark; ++ struct device *dev = &isys->adev->auxdev.dev; ++ struct v4l2_mbus_framefmt format; ++ struct v4l2_subdev *esd; ++ struct v4l2_control hb = { .id = V4L2_CID_HBLANK, .value = 0 }; ++ unsigned int bpp, lanes; ++ s64 link_freq = 0; ++ u64 pixel_rate = 0; ++ int ret; ++ ++ if (!state) ++ return; ++ ++ esd = media_entity_to_v4l2_subdev(av->stream->source_entity); ++ ++ av->watermark.width = av->mpix.width; ++ av->watermark.height = av->mpix.height; ++ av->watermark.sram_gran_shift = isys->pdata->ipdata->sram_gran_shift; ++ av->watermark.sram_gran_size = isys->pdata->ipdata->sram_gran_size; ++ ++ ret = v4l2_g_ctrl(esd->ctrl_handler, &hb); ++ if (!ret && hb.value >= 0) ++ av->watermark.hblank = hb.value; ++ else ++ av->watermark.hblank = 0; ++ ++ csi2 = ipu6_isys_subdev_to_csi2(av->stream->asd); ++ link_freq = ipu6_isys_csi2_get_link_freq(csi2); ++ if (link_freq > 0) { ++ lanes = csi2->nlanes; ++ ret = ipu6_isys_get_stream_pad_fmt(&csi2->asd.sd, 0, ++ av->source_stream, &format); ++ if (!ret) { ++ bpp = ipu6_isys_mbus_code_to_bpp(format.code); ++ pixel_rate = mul_u64_u32_div(link_freq, lanes * 2, bpp); ++ } ++ } ++ ++ av->watermark.pixel_rate = pixel_rate; ++ ++ if (!pixel_rate) { ++ mutex_lock(&iwake_watermark->mutex); ++ iwake_watermark->force_iwake_disable = true; ++ mutex_unlock(&iwake_watermark->mutex); ++ dev_warn(dev, "unexpected pixel_rate from %s, disable iwake.\n", ++ av->stream->source_entity->name); ++ } ++} ++ ++static void calculate_stream_datarate(struct ipu6_isys_video *av) ++{ ++ struct video_stream_watermark *watermark = &av->watermark; ++ u32 bpp = av->pfmt->bpp; ++ u32 pages_per_line, pb_bytes_per_line, pixels_per_line, bytes_per_line; ++ u64 line_time_ns, stream_data_rate; ++ u16 shift, size; ++ ++ shift = watermark->sram_gran_shift; ++ size = watermark->sram_gran_size; ++ ++ pixels_per_line = watermark->width + watermark->hblank; ++ line_time_ns = div_u64(pixels_per_line * NSEC_PER_SEC, ++ watermark->pixel_rate); ++ bytes_per_line = watermark->width * bpp / 8; ++ pages_per_line = DIV_ROUND_UP(bytes_per_line, size); ++ pb_bytes_per_line = pages_per_line << shift; ++ stream_data_rate = div64_u64(pb_bytes_per_line * 1000, line_time_ns); ++ ++ watermark->stream_data_rate = stream_data_rate; ++} ++ ++void ipu6_isys_update_stream_watermark(struct ipu6_isys_video *av, bool state) ++{ ++ struct isys_iwake_watermark *iwake_watermark = ++ &av->isys->iwake_watermark; ++ ++ if (!av->watermark.pixel_rate) ++ return; ++ ++ if (state) { ++ calculate_stream_datarate(av); ++ mutex_lock(&iwake_watermark->mutex); ++ list_add(&av->watermark.stream_node, ++ &iwake_watermark->video_list); ++ mutex_unlock(&iwake_watermark->mutex); ++ } else { ++ av->watermark.stream_data_rate = 0; ++ mutex_lock(&iwake_watermark->mutex); ++ list_del(&av->watermark.stream_node); ++ mutex_unlock(&iwake_watermark->mutex); ++ } ++ ++ update_watermark_setting(av->isys); ++} ++ ++void ipu6_isys_put_stream(struct ipu6_isys_stream *stream) ++{ ++ struct device *dev = &stream->isys->adev->auxdev.dev; ++ unsigned int i; ++ unsigned long flags; ++ ++ if (!stream) { ++ dev_err(dev, "no available stream\n"); ++ return; ++ } ++ ++ spin_lock_irqsave(&stream->isys->streams_lock, flags); ++ for (i = 0; i < IPU6_ISYS_MAX_STREAMS; i++) { ++ if (&stream->isys->streams[i] == stream) { ++ if (stream->isys->streams_ref_count[i] > 0) ++ stream->isys->streams_ref_count[i]--; ++ else ++ dev_warn(dev, "invalid stream %d\n", i); ++ ++ break; ++ } ++ } ++ spin_unlock_irqrestore(&stream->isys->streams_lock, flags); ++} ++ ++static struct ipu6_isys_stream * ++ipu6_isys_get_stream(struct ipu6_isys_video *av, struct ipu6_isys_subdev *asd) ++{ ++ struct ipu6_isys_stream *stream = NULL; ++ struct ipu6_isys *isys = av->isys; ++ unsigned long flags; ++ unsigned int i; ++ u8 vc = av->vc; ++ ++ if (!isys) ++ return NULL; ++ ++ spin_lock_irqsave(&isys->streams_lock, flags); ++ for (i = 0; i < IPU6_ISYS_MAX_STREAMS; i++) { ++ if (isys->streams_ref_count[i] && isys->streams[i].vc == vc && ++ isys->streams[i].asd == asd) { ++ isys->streams_ref_count[i]++; ++ stream = &isys->streams[i]; ++ break; ++ } ++ } ++ ++ if (!stream) { ++ for (i = 0; i < IPU6_ISYS_MAX_STREAMS; i++) { ++ if (!isys->streams_ref_count[i]) { ++ isys->streams_ref_count[i]++; ++ stream = &isys->streams[i]; ++ stream->vc = vc; ++ stream->asd = asd; ++ break; ++ } ++ } ++ } ++ spin_unlock_irqrestore(&isys->streams_lock, flags); ++ ++ return stream; ++} ++ ++struct ipu6_isys_stream * ++ipu6_isys_query_stream_by_handle(struct ipu6_isys *isys, u8 stream_handle) ++{ ++ unsigned long flags; ++ struct ipu6_isys_stream *stream = NULL; ++ ++ if (!isys) ++ return NULL; ++ ++ if (stream_handle >= IPU6_ISYS_MAX_STREAMS) { ++ dev_err(&isys->adev->auxdev.dev, ++ "stream_handle %d is invalid\n", stream_handle); ++ return NULL; ++ } ++ ++ spin_lock_irqsave(&isys->streams_lock, flags); ++ if (isys->streams_ref_count[stream_handle] > 0) { ++ isys->streams_ref_count[stream_handle]++; ++ stream = &isys->streams[stream_handle]; ++ } ++ spin_unlock_irqrestore(&isys->streams_lock, flags); ++ ++ return stream; ++} ++ ++struct ipu6_isys_stream * ++ipu6_isys_query_stream_by_source(struct ipu6_isys *isys, int source, u8 vc) ++{ ++ struct ipu6_isys_stream *stream = NULL; ++ unsigned long flags; ++ unsigned int i; ++ ++ if (!isys) ++ return NULL; ++ ++ if (source < 0) { ++ dev_err(&stream->isys->adev->auxdev.dev, ++ "query stream with invalid port number\n"); ++ return NULL; ++ } ++ ++ spin_lock_irqsave(&isys->streams_lock, flags); ++ for (i = 0; i < IPU6_ISYS_MAX_STREAMS; i++) { ++ if (!isys->streams_ref_count[i]) ++ continue; ++ ++ if (isys->streams[i].stream_source == source && ++ isys->streams[i].vc == vc) { ++ stream = &isys->streams[i]; ++ isys->streams_ref_count[i]++; ++ break; ++ } ++ } ++ spin_unlock_irqrestore(&isys->streams_lock, flags); ++ ++ return stream; ++} ++ ++static u64 get_stream_mask_by_pipeline(struct ipu6_isys_video *av) ++{ ++ struct media_pipeline *pipeline = ++ media_entity_pipeline(&av->vdev.entity); ++ struct media_entity *entity; ++ unsigned int i; ++ u64 stream_mask = 0; ++ ++ for (i = 0; i < NR_OF_VIDEO_DEVICE; i++) { ++ entity = &av->isys->av[i].vdev.entity; ++ if (pipeline == media_entity_pipeline(entity)) ++ stream_mask |= BIT_ULL(av->isys->av[i].source_stream); ++ } ++ ++ return stream_mask; ++} ++ ++int ipu6_isys_video_set_streaming(struct ipu6_isys_video *av, int state, ++ struct ipu6_isys_buffer_list *bl) ++{ ++ struct v4l2_subdev_krouting *routing; ++ struct ipu6_isys_stream *stream = av->stream; ++ struct v4l2_subdev_state *subdev_state; ++ struct device *dev = &av->isys->adev->auxdev.dev; ++ struct v4l2_subdev *sd = NULL; ++ struct v4l2_subdev *ssd = NULL; ++ struct media_pad *r_pad; ++ struct media_pad *s_pad = NULL; ++ u32 sink_pad, sink_stream; ++ u64 r_stream; ++ u64 stream_mask = 0; ++ int ret = 0; ++ ++ dev_dbg(dev, "set stream: %d\n", state); ++ ++ if (WARN(!stream->source_entity, "No source entity for stream\n")) ++ return -ENODEV; ++ ++ ssd = media_entity_to_v4l2_subdev(stream->source_entity); ++ sd = &stream->asd->sd; ++ r_pad = media_pad_remote_pad_first(&av->pad); ++ r_stream = ipu6_isys_get_src_stream_by_src_pad(sd, r_pad->index); ++ ++ subdev_state = v4l2_subdev_lock_and_get_active_state(sd); ++ routing = &subdev_state->routing; ++ ret = v4l2_subdev_routing_find_opposite_end(routing, r_pad->index, ++ r_stream, &sink_pad, ++ &sink_stream); ++ v4l2_subdev_unlock_state(subdev_state); ++ if (ret) ++ return ret; ++ ++ s_pad = media_pad_remote_pad_first(&stream->asd->pad[sink_pad]); ++ ++ stream_mask = get_stream_mask_by_pipeline(av); ++ if (!state) { ++ stop_streaming_firmware(av); ++ ++ /* stop external sub-device now. */ ++ dev_dbg(dev, "disable streams 0x%llx of %s\n", stream_mask, ++ ssd->name); ++ ret = v4l2_subdev_disable_streams(ssd, s_pad->index, ++ stream_mask); ++ if (ret) { ++ dev_err(dev, "disable streams of %s failed with %d\n", ++ ssd->name, ret); ++ return ret; ++ } ++ ++ /* stop sub-device which connects with video */ ++ dev_dbg(dev, "stream off entity %s pad:%d\n", sd->name, ++ r_pad->index); ++ ret = v4l2_subdev_call(sd, video, s_stream, state); ++ if (ret) { ++ dev_err(dev, "stream off %s failed with %d\n", sd->name, ++ ret); ++ return ret; ++ } ++ close_streaming_firmware(av); ++ } else { ++ ret = start_stream_firmware(av, bl); ++ if (ret) { ++ dev_err(dev, "start stream of firmware failed\n"); ++ goto out_clear_stream_watermark; ++ } ++ ++ /* start sub-device which connects with video */ ++ dev_dbg(dev, "stream on %s pad %d\n", sd->name, r_pad->index); ++ ret = v4l2_subdev_call(sd, video, s_stream, state); ++ if (ret) { ++ dev_err(dev, "stream on %s failed with %d\n", sd->name, ++ ret); ++ goto out_media_entity_stop_streaming_firmware; ++ } ++ ++ /* start external sub-device now. */ ++ dev_dbg(dev, "enable streams 0x%llx of %s\n", stream_mask, ++ ssd->name); ++ ret = v4l2_subdev_enable_streams(ssd, s_pad->index, ++ stream_mask); ++ if (ret) { ++ dev_err(dev, ++ "enable streams 0x%llx of %s failed with %d\n", ++ stream_mask, stream->source_entity->name, ret); ++ goto out_media_entity_stop_streaming; ++ } ++ } ++ ++ av->streaming = state; ++ ++ return 0; ++ ++out_media_entity_stop_streaming: ++ v4l2_subdev_disable_streams(sd, r_pad->index, BIT(r_stream)); ++ ++out_media_entity_stop_streaming_firmware: ++ stop_streaming_firmware(av); ++ ++out_clear_stream_watermark: ++ ipu6_isys_update_stream_watermark(av, 0); ++ ++ return ret; ++} ++ ++static const struct v4l2_ioctl_ops ioctl_ops_mplane = { ++ .vidioc_querycap = ipu6_isys_vidioc_querycap, ++ .vidioc_enum_fmt_vid_cap = ipu6_isys_vidioc_enum_fmt, ++ .vidioc_enum_framesizes = ipu6_isys_vidioc_enum_framesizes, ++ .vidioc_g_fmt_vid_cap_mplane = vidioc_g_fmt_vid_cap_mplane, ++ .vidioc_s_fmt_vid_cap_mplane = vidioc_s_fmt_vid_cap_mplane, ++ .vidioc_try_fmt_vid_cap_mplane = vidioc_try_fmt_vid_cap_mplane, ++ .vidioc_reqbufs = vb2_ioctl_reqbufs, ++ .vidioc_create_bufs = vb2_ioctl_create_bufs, ++ .vidioc_prepare_buf = vb2_ioctl_prepare_buf, ++ .vidioc_querybuf = vb2_ioctl_querybuf, ++ .vidioc_qbuf = vb2_ioctl_qbuf, ++ .vidioc_dqbuf = vb2_ioctl_dqbuf, ++ .vidioc_streamon = vb2_ioctl_streamon, ++ .vidioc_streamoff = vb2_ioctl_streamoff, ++ .vidioc_expbuf = vb2_ioctl_expbuf, ++}; ++ ++static const struct media_entity_operations entity_ops = { ++ .link_validate = link_validate, ++}; ++ ++static const struct v4l2_file_operations isys_fops = { ++ .owner = THIS_MODULE, ++ .poll = vb2_fop_poll, ++ .unlocked_ioctl = video_ioctl2, ++ .mmap = vb2_fop_mmap, ++ .open = video_open, ++ .release = video_release, ++}; ++ ++int ipu6_isys_fw_open(struct ipu6_isys *isys) ++{ ++ struct ipu6_bus_device *adev = isys->adev; ++ const struct ipu6_isys_internal_pdata *ipdata = isys->pdata->ipdata; ++ int ret; ++ ++ ret = pm_runtime_resume_and_get(&adev->auxdev.dev); ++ if (ret < 0) ++ return ret; ++ ++ mutex_lock(&isys->mutex); ++ ++ if (isys->ref_count++) ++ goto unlock; ++ ++ ipu6_configure_spc(adev->isp, &ipdata->hw_variant, ++ IPU6_CPD_PKG_DIR_ISYS_SERVER_IDX, isys->pdata->base, ++ adev->pkg_dir, adev->pkg_dir_dma_addr); ++ ++ /* ++ * Buffers could have been left to wrong queue at last closure. ++ * Move them now back to empty buffer queue. ++ */ ++ ipu6_cleanup_fw_msg_bufs(isys); ++ ++ if (isys->fwcom) { ++ /* ++ * Something went wrong in previous shutdown. As we are now ++ * restarting isys we can safely delete old context. ++ */ ++ dev_warn(&adev->auxdev.dev, "clearing old context\n"); ++ ipu6_fw_isys_cleanup(isys); ++ } ++ ++ ret = ipu6_fw_isys_init(isys, ipdata->num_parallel_streams); ++ if (ret < 0) ++ goto out; ++ ++unlock: ++ mutex_unlock(&isys->mutex); ++ ++ return 0; ++ ++out: ++ isys->ref_count--; ++ mutex_unlock(&isys->mutex); ++ pm_runtime_put(&adev->auxdev.dev); ++ ++ return ret; ++} ++ ++void ipu6_isys_fw_close(struct ipu6_isys *isys) ++{ ++ mutex_lock(&isys->mutex); ++ ++ isys->ref_count--; ++ if (!isys->ref_count) { ++ ipu6_fw_isys_close(isys); ++ if (isys->fwcom) { ++ isys->need_reset = true; ++ dev_warn(&isys->adev->auxdev.dev, ++ "failed to close fw isys\n"); ++ } ++ } ++ ++ mutex_unlock(&isys->mutex); ++ ++ if (isys->need_reset) ++ pm_runtime_put_sync(&isys->adev->auxdev.dev); ++ else ++ pm_runtime_put(&isys->adev->auxdev.dev); ++} ++ ++int ipu6_isys_setup_video(struct ipu6_isys_video *av, ++ struct media_entity **source_entity, int *nr_queues) ++{ ++ struct device *dev = &av->isys->adev->auxdev.dev; ++ struct v4l2_mbus_frame_desc_entry entry; ++ struct v4l2_subdev_route *route = NULL; ++ struct v4l2_subdev_route *r; ++ struct v4l2_subdev_state *state; ++ struct ipu6_isys_subdev *asd; ++ struct v4l2_subdev *remote_sd; ++ struct media_pipeline *pipeline; ++ struct media_pad *source_pad, *remote_pad; ++ int ret = -EINVAL; ++ ++ remote_pad = media_pad_remote_pad_first(&av->pad); ++ if (!remote_pad) { ++ dev_dbg(dev, "failed to get remote pad\n"); ++ return -ENODEV; ++ } ++ ++ remote_sd = media_entity_to_v4l2_subdev(remote_pad->entity); ++ asd = to_ipu6_isys_subdev(remote_sd); ++ source_pad = media_pad_remote_pad_first(&remote_pad->entity->pads[0]); ++ if (!source_pad) { ++ dev_dbg(dev, "No external source entity\n"); ++ return -ENODEV; ++ } ++ ++ *source_entity = source_pad->entity; ++ ++ /* Find the root */ ++ state = v4l2_subdev_lock_and_get_active_state(remote_sd); ++ for_each_active_route(&state->routing, r) { ++ if (r->source_pad != remote_pad->index) ++ continue; ++ ++ route = r; ++ break; ++ } ++ ++ if (!route) { ++ v4l2_subdev_unlock_state(state); ++ dev_dbg(dev, "Failed to find route\n"); ++ return -ENODEV; ++ } ++ v4l2_subdev_unlock_state(state); ++ av->source_stream = route->sink_stream; ++ ++ ret = ipu6_isys_csi2_get_remote_desc(av->source_stream, ++ to_ipu6_isys_csi2(asd), ++ *source_entity, &entry, ++ nr_queues); ++ if (ret == -ENOIOCTLCMD) { ++ av->vc = 0; ++ av->dt = ipu6_isys_mbus_code_to_mipi(av->pfmt->code); ++ *nr_queues = 1; ++ } else if (!ret) { ++ dev_dbg(dev, "Framedesc: stream %u, len %u, vc %u, dt %#x\n", ++ entry.stream, entry.length, entry.bus.csi2.vc, ++ entry.bus.csi2.dt); ++ ++ av->vc = entry.bus.csi2.vc; ++ av->dt = entry.bus.csi2.dt; ++ } else { ++ dev_err(dev, "failed to get remote frame desc\n"); ++ return ret; ++ } ++ ++ pipeline = media_entity_pipeline(&av->vdev.entity); ++ if (!pipeline) ++ ret = video_device_pipeline_alloc_start(&av->vdev); ++ else ++ ret = video_device_pipeline_start(&av->vdev, pipeline); ++ if (ret < 0) { ++ dev_dbg(dev, "media pipeline start failed\n"); ++ return ret; ++ } ++ ++ av->stream = ipu6_isys_get_stream(av, asd); ++ if (!av->stream) { ++ video_device_pipeline_stop(&av->vdev); ++ dev_err(dev, "no available stream for firmware\n"); ++ return -EINVAL; ++ } ++ ++ return 0; ++} ++ ++/* ++ * Do everything that's needed to initialise things related to video ++ * buffer queue, video node, and the related media entity. The caller ++ * is expected to assign isys field and set the name of the video ++ * device. ++ */ ++int ipu6_isys_video_init(struct ipu6_isys_video *av) ++{ ++ struct v4l2_format format = { ++ .type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE, ++ .fmt.pix_mp = { ++ .width = 1920, ++ .height = 1080, ++ }, ++ }; ++ int ret; ++ ++ mutex_init(&av->mutex); ++ av->vdev.device_caps = V4L2_CAP_STREAMING | V4L2_CAP_IO_MC | ++ V4L2_CAP_VIDEO_CAPTURE_MPLANE; ++ av->vdev.vfl_dir = VFL_DIR_RX; ++ ++ ret = ipu6_isys_queue_init(&av->aq); ++ if (ret) ++ goto out_free_watermark; ++ ++ av->pad.flags = MEDIA_PAD_FL_SINK | MEDIA_PAD_FL_MUST_CONNECT; ++ ret = media_entity_pads_init(&av->vdev.entity, 1, &av->pad); ++ if (ret) ++ goto out_vb2_queue_release; ++ ++ av->vdev.entity.ops = &entity_ops; ++ av->vdev.release = video_device_release_empty; ++ av->vdev.fops = &isys_fops; ++ av->vdev.v4l2_dev = &av->isys->v4l2_dev; ++ if (!av->vdev.ioctl_ops) ++ av->vdev.ioctl_ops = &ioctl_ops_mplane; ++ av->vdev.queue = &av->aq.vbq; ++ av->vdev.lock = &av->mutex; ++ ++ ipu6_isys_video_try_fmt_vid_mplane(av, &format.fmt.pix_mp); ++ av->mpix = format.fmt.pix_mp; ++ ++ set_bit(V4L2_FL_USES_V4L2_FH, &av->vdev.flags); ++ video_set_drvdata(&av->vdev, av); ++ ++ ret = video_register_device(&av->vdev, VFL_TYPE_VIDEO, -1); ++ if (ret) ++ goto out_media_entity_cleanup; ++ ++ return ret; ++ ++out_media_entity_cleanup: ++ vb2_video_unregister_device(&av->vdev); ++ media_entity_cleanup(&av->vdev.entity); ++ ++out_vb2_queue_release: ++ vb2_queue_release(&av->aq.vbq); ++ ++out_free_watermark: ++ mutex_destroy(&av->mutex); ++ ++ return ret; ++} ++ ++void ipu6_isys_video_cleanup(struct ipu6_isys_video *av) ++{ ++ vb2_video_unregister_device(&av->vdev); ++ media_entity_cleanup(&av->vdev.entity); ++ mutex_destroy(&av->mutex); ++} +diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys-video.h b/drivers/media/pci/intel/ipu6/ipu6-isys-video.h +new file mode 100644 +index 000000000000..21cd33c7e277 +--- /dev/null ++++ b/drivers/media/pci/intel/ipu6/ipu6-isys-video.h +@@ -0,0 +1,136 @@ ++/* SPDX-License-Identifier: GPL-2.0-only */ ++/* Copyright (C) 2013 - 2023 Intel Corporation */ ++ ++#ifndef IPU6_ISYS_VIDEO_H ++#define IPU6_ISYS_VIDEO_H ++ ++#include <linux/atomic.h> ++#include <linux/completion.h> ++#include <linux/container_of.h> ++#include <linux/list.h> ++#include <linux/mutex.h> ++ ++#include <media/media-entity.h> ++#include <media/v4l2-dev.h> ++ ++#include "ipu6-isys-queue.h" ++ ++#define IPU6_ISYS_OUTPUT_PINS 11 ++#define IPU6_ISYS_MAX_PARALLEL_SOF 2 ++#define NR_OF_VIDEO_DEVICE 31 ++ ++struct file; ++struct ipu6_isys; ++struct ipu6_isys_subdev; ++ ++struct ipu6_isys_pixelformat { ++ u32 pixelformat; ++ u32 bpp; ++ u32 bpp_packed; ++ u32 code; ++ u32 css_pixelformat; ++}; ++ ++struct sequence_info { ++ unsigned int sequence; ++ u64 timestamp; ++}; ++ ++struct output_pin_data { ++ void (*pin_ready)(struct ipu6_isys_stream *stream, ++ struct ipu6_fw_isys_resp_info_abi *info); ++ struct ipu6_isys_queue *aq; ++}; ++ ++/* ++ * Align with firmware stream. Each stream represents a CSI virtual channel. ++ * May map to multiple video devices ++ */ ++struct ipu6_isys_stream { ++ struct mutex mutex; ++ struct media_entity *source_entity; ++ atomic_t sequence; ++ unsigned int seq_index; ++ struct sequence_info seq[IPU6_ISYS_MAX_PARALLEL_SOF]; ++ int stream_source; ++ int stream_handle; ++ unsigned int nr_output_pins; ++ struct ipu6_isys_subdev *asd; ++ ++ int nr_queues; /* Number of capture queues */ ++ int nr_streaming; ++ int streaming; /* Has streaming been really started? */ ++ struct list_head queues; ++ struct completion stream_open_completion; ++ struct completion stream_close_completion; ++ struct completion stream_start_completion; ++ struct completion stream_stop_completion; ++ struct ipu6_isys *isys; ++ ++ struct output_pin_data output_pins[IPU6_ISYS_OUTPUT_PINS]; ++ int error; ++ u8 vc; ++}; ++ ++struct video_stream_watermark { ++ u32 width; ++ u32 height; ++ u32 hblank; ++ u32 frame_rate; ++ u64 pixel_rate; ++ u64 stream_data_rate; ++ u16 sram_gran_shift; ++ u16 sram_gran_size; ++ struct list_head stream_node; ++}; ++ ++struct ipu6_isys_video { ++ struct ipu6_isys_queue aq; ++ /* Serialise access to other fields in the struct. */ ++ struct mutex mutex; ++ struct media_pad pad; ++ struct video_device vdev; ++ struct v4l2_pix_format_mplane mpix; ++ const struct ipu6_isys_pixelformat *pfmt; ++ struct ipu6_isys *isys; ++ struct ipu6_isys_stream *stream; ++ unsigned int streaming; ++ struct video_stream_watermark watermark; ++ u32 source_stream; ++ u8 vc; ++ u8 dt; ++}; ++ ++#define ipu6_isys_queue_to_video(__aq) \ ++ container_of(__aq, struct ipu6_isys_video, aq) ++ ++extern const struct ipu6_isys_pixelformat ipu6_isys_pfmts[]; ++extern const struct ipu6_isys_pixelformat ipu6_isys_pfmts_packed[]; ++ ++int ipu6_isys_vidioc_querycap(struct file *file, void *fh, ++ struct v4l2_capability *cap); ++ ++int ipu6_isys_vidioc_enum_fmt(struct file *file, void *fh, ++ struct v4l2_fmtdesc *f); ++int ipu6_isys_video_prepare_stream(struct ipu6_isys_video *av, ++ struct media_entity *source_entity, ++ int nr_queues); ++int ipu6_isys_video_set_streaming(struct ipu6_isys_video *av, int state, ++ struct ipu6_isys_buffer_list *bl); ++int ipu6_isys_fw_open(struct ipu6_isys *isys); ++void ipu6_isys_fw_close(struct ipu6_isys *isys); ++int ipu6_isys_setup_video(struct ipu6_isys_video *av, ++ struct media_entity **source_entity, int *nr_queues); ++int ipu6_isys_video_init(struct ipu6_isys_video *av); ++void ipu6_isys_video_cleanup(struct ipu6_isys_video *av); ++void ipu6_isys_put_stream(struct ipu6_isys_stream *stream); ++struct ipu6_isys_stream * ++ipu6_isys_query_stream_by_handle(struct ipu6_isys *isys, u8 stream_handle); ++struct ipu6_isys_stream * ++ipu6_isys_query_stream_by_source(struct ipu6_isys *isys, int source, u8 vc); ++ ++void ipu6_isys_configure_stream_watermark(struct ipu6_isys_video *av, ++ bool state); ++void ipu6_isys_update_stream_watermark(struct ipu6_isys_video *av, bool state); ++ ++#endif /* IPU6_ISYS_VIDEO_H */ +-- +2.43.0 + +From abf929b4fbd35689080a8629c6bfebe0de699fe5 Mon Sep 17 00:00:00 2001 +From: Bingbu Cao <bingbu.cao@intel.com> +Date: Thu, 11 Jan 2024 14:55:26 +0800 +Subject: [PATCH 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 <bingbu.cao@intel.com> +Signed-off-by: Andreas Helbech Kleist <andreaskleist@gmail.com> +Link: https://lore.kernel.org/r/20240111065531.2418836-13-bingbu.cao@intel.com +Signed-off-by: Hans de Goede <hdegoede@redhat.com> +--- + drivers/media/pci/intel/Kconfig | 1 + + drivers/media/pci/intel/Makefile | 1 + + drivers/media/pci/intel/ipu6/Kconfig | 17 +++++++++++++++++ + drivers/media/pci/intel/ipu6/Makefile | 23 +++++++++++++++++++++++ + 4 files changed, 42 insertions(+) + create mode 100644 drivers/media/pci/intel/ipu6/Kconfig + create mode 100644 drivers/media/pci/intel/ipu6/Makefile + +diff --git a/drivers/media/pci/intel/Kconfig b/drivers/media/pci/intel/Kconfig +index ee4684159d3d..04cb3d253486 100644 +--- a/drivers/media/pci/intel/Kconfig ++++ b/drivers/media/pci/intel/Kconfig +@@ -1,6 +1,7 @@ + # SPDX-License-Identifier: GPL-2.0-only + + source "drivers/media/pci/intel/ipu3/Kconfig" ++source "drivers/media/pci/intel/ipu6/Kconfig" + source "drivers/media/pci/intel/ivsc/Kconfig" + + config IPU_BRIDGE +diff --git a/drivers/media/pci/intel/Makefile b/drivers/media/pci/intel/Makefile +index f199a97e1d78..3a2cc6567159 100644 +--- a/drivers/media/pci/intel/Makefile ++++ b/drivers/media/pci/intel/Makefile +@@ -5,3 +5,4 @@ + obj-$(CONFIG_IPU_BRIDGE) += ipu-bridge.o + obj-y += ipu3/ + obj-y += ivsc/ ++obj-$(CONFIG_VIDEO_INTEL_IPU6) += ipu6/ +diff --git a/drivers/media/pci/intel/ipu6/Kconfig b/drivers/media/pci/intel/ipu6/Kconfig +new file mode 100644 +index 000000000000..5cb4f3c2d59f +--- /dev/null ++++ b/drivers/media/pci/intel/ipu6/Kconfig +@@ -0,0 +1,17 @@ ++config VIDEO_INTEL_IPU6 ++ tristate "Intel IPU6 driver" ++ depends on ACPI || COMPILE_TEST ++ depends on MEDIA_SUPPORT ++ depends on MEDIA_PCI_SUPPORT ++ depends on X86 && X86_64 ++ select IOMMU_IOVA ++ select VIDEO_V4L2_SUBDEV_API ++ select VIDEOBUF2_DMA_CONTIG ++ select V4L2_FWNODE ++ select IPU_BRIDGE ++ help ++ This is the 6th Gen Intel Image Processing Unit, found in Intel SoCs ++ and used for capturing images and video from camera sensors. ++ ++ To compile this driver, say Y here! It contains 2 modules - ++ intel_ipu6 and intel_ipu6_isys. +diff --git a/drivers/media/pci/intel/ipu6/Makefile b/drivers/media/pci/intel/ipu6/Makefile +new file mode 100644 +index 000000000000..a821b0a1567f +--- /dev/null ++++ b/drivers/media/pci/intel/ipu6/Makefile +@@ -0,0 +1,23 @@ ++# SPDX-License-Identifier: GPL-2.0-only ++ ++intel-ipu6-y := ipu6.o \ ++ ipu6-bus.o \ ++ ipu6-dma.o \ ++ ipu6-mmu.o \ ++ ipu6-buttress.o \ ++ ipu6-cpd.o \ ++ ipu6-fw-com.o ++ ++obj-$(CONFIG_VIDEO_INTEL_IPU6) += intel-ipu6.o ++ ++intel-ipu6-isys-y := ipu6-isys.o \ ++ ipu6-isys-csi2.o \ ++ ipu6-fw-isys.o \ ++ ipu6-isys-video.o \ ++ ipu6-isys-queue.o \ ++ ipu6-isys-subdev.o \ ++ ipu6-isys-mcd-phy.o \ ++ ipu6-isys-jsl-phy.o \ ++ ipu6-isys-dwc-phy.o ++ ++obj-$(CONFIG_VIDEO_INTEL_IPU6) += intel-ipu6-isys.o +-- +2.43.0 + +From 59872e5aa96479948da4b1d04a10101d8cb86da2 Mon Sep 17 00:00:00 2001 +From: Bingbu Cao <bingbu.cao@intel.com> +Date: Thu, 11 Jan 2024 14:55:27 +0800 +Subject: [PATCH 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 <bingbu.cao@intel.com> +Link: https://lore.kernel.org/r/20240111065531.2418836-14-bingbu.cao@intel.com +Signed-off-by: Hans de Goede <hdegoede@redhat.com> +--- + 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 <sakari.ailus@linux.intel.com> ++M: Bingbu Cao <bingbu.cao@intel.com> ++R: Tianshu Qiu <tian.shu.qiu@intel.com> ++L: linux-media@vger.kernel.org ++S: Maintained ++T: git git://linuxtv.org/media_tree.git ++F: Documentation/admin-guide/media/ipu6-isys.rst ++F: drivers/media/pci/intel/ipu6/ ++ + INTEL ISHTP ECLITE DRIVER + M: Sumesh K Naduvalath <sumesh.k.naduvalath@intel.com> + L: platform-driver-x86@vger.kernel.org +-- +2.43.0 + +From 7acc1618d2f9cbb0d6b07ce9a1eace25663b9fb2 Mon Sep 17 00:00:00 2001 +From: Bingbu Cao <bingbu.cao@intel.com> +Date: Thu, 11 Jan 2024 14:55:28 +0800 +Subject: [PATCH 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 <bingbu.cao@intel.com> +Link: https://lore.kernel.org/r/20240111065531.2418836-15-bingbu.cao@intel.com +Signed-off-by: Hans de Goede <hdegoede@redhat.com> +--- + Documentation/admin-guide/media/ipu6-isys.rst | 158 ++++++++++++++++ + .../admin-guide/media/ipu6_isys_graph.svg | 174 ++++++++++++++++++ + .../admin-guide/media/v4l-drivers.rst | 1 + + 3 files changed, 333 insertions(+) + create mode 100644 Documentation/admin-guide/media/ipu6-isys.rst + create mode 100644 Documentation/admin-guide/media/ipu6_isys_graph.svg + +diff --git a/Documentation/admin-guide/media/ipu6-isys.rst b/Documentation/admin-guide/media/ipu6-isys.rst +new file mode 100644 +index 000000000000..5e78ab88c649 +--- /dev/null ++++ b/Documentation/admin-guide/media/ipu6-isys.rst +@@ -0,0 +1,158 @@ ++.. SPDX-License-Identifier: GPL-2.0 ++ ++.. include:: <isonum.txt> ++ ++======================================================== ++Intel Image Processing Unit 6 (IPU6) Input System driver ++======================================================== ++ ++Copyright |copy| 2023 Intel Corporation ++ ++Introduction ++============ ++ ++This file documents the Intel IPU6 (6th generation Image Processing Unit) ++Input System (MIPI CSI2 receiver) drivers located under ++drivers/media/pci/intel/ipu6. ++ ++The Intel IPU6 can be found in certain Intel Chipsets but not in all SKUs: ++ ++* TigerLake ++* JasperLake ++* AlderLake ++* RaptorLake ++* MeteorLake ++ ++Intel IPU6 is made up of two components - Input System (ISYS) and Processing ++System (PSYS). ++ ++The Input System mainly works as MIPI CSI2 receiver which receives and ++processes the imaging data from the sensors and outputs the frames to memory. ++ ++There are 2 driver modules - intel_ipu6 and intel_ipu6_isys. intel_ipu6 is an ++IPU6 common driver which does PCI configuration, firmware loading and parsing, ++firmware authentication, DMA mapping and IPU-MMU (internal Memory mapping Unit) ++configuration. intel_ipu6_isys implements V4L2, Media Controller and V4L2 ++sub-device interfaces. The IPU6 ISYS driver supports camera sensors connected ++to the IPU6 ISYS through V4L2 sub-device sensor drivers. ++ ++.. Note:: See Documentation/driver-api/media/drivers/ipu6.rst for more ++ information about the IPU6 hardware. ++ ++ ++Input system driver ++=================== ++ ++The input System driver mainly configures CSI2 DPHY, constructs the firmware ++stream configuration, sends commands to firmware, gets response from hardware ++and firmware and then returns buffers to user. ++The ISYS is represented as several V4L2 sub-devices - 'Intel IPU6 CSI2 $port', ++which provide V4L2 subdev interfaces to the user space, there are also several ++video nodes for each CSI-2 stream capture - 'Intel IPU6 ISYS capture $num' which ++provide interface to user to set formats, queue buffers and streaming. ++ ++.. kernel-figure:: ipu6_isys_graph.svg ++ :alt: ipu6 isys media graph with multiple streams support ++ ++ ipu6 isys media graph with multiple streams support ++ ++Capturing frames by IPU6 ISYS ++----------------------------- ++ ++IPU6 ISYS is used to capture frames from the camera sensors connected to the ++CSI2 ports. The supported input formats of ISYS are listed in table below: ++ ++.. tabularcolumns:: |p{0.8cm}|p{4.0cm}|p{4.0cm}| ++ ++.. flat-table:: ++ :header-rows: 1 ++ ++ * - IPU6 ISYS supported input formats ++ ++ * - RGB565, RGB888 ++ ++ * - UYVY8, YUYV8 ++ ++ * - RAW8, RAW10, RAW12 ++ ++.. _ipu6_isys_capture_examples: ++ ++Examples ++~~~~~~~~ ++Here is an example of IPU6 ISYS raw capture on Dell XPS 9315 laptop. On this ++machine, ov01a10 sensor is connected to IPU ISYS CSI2 port 2, which can ++generate images at sBGGR10 with resolution 1280x800. ++ ++Using the media controller APIs, we can configure ov01a10 sensor by ++media-ctl [#f1]_ and yavta [#f2]_ to transmit frames to IPU6 ISYS. ++ ++.. code-block:: none ++ ++ # Example 1 capture frame from ov01a10 camera sensor ++ # This example assumes /dev/media0 as the IPU ISYS media device ++ export MDEV=/dev/media0 ++ ++ # Establish the link for the media devices using media-ctl ++ media-ctl -d $MDEV -l "\"ov01a10 3-0036\":0 -> \"Intel IPU6 CSI2 2\":0[1]" ++ ++ # Set the format for the media devices ++ media-ctl -d $MDEV -V "ov01a10:0 [fmt:SBGGR10/1280x800]" ++ media-ctl -d $MDEV -V "Intel IPU6 CSI2 2:0 [fmt:SBGGR10/1280x800]" ++ media-ctl -d $MDEV -V "Intel IPU6 CSI2 2:1 [fmt:SBGGR10/1280x800]" ++ ++Once the media pipeline is configured, desired sensor specific settings ++(such as exposure and gain settings) can be set, using the yavta tool. ++ ++e.g ++ ++.. code-block:: none ++ ++ # and that ov01a10 sensor is connected to i2c bus 3 with address 0x36 ++ export SDEV=$(media-ctl -d $MDEV -e "ov01a10 3-0036") ++ ++ yavta -w 0x009e0903 400 $SDEV ++ yavta -w 0x009e0913 1000 $SDEV ++ yavta -w 0x009e0911 2000 $SDEV ++ ++Once the desired sensor settings are set, frame captures can be done as below. ++ ++e.g ++ ++.. code-block:: none ++ ++ yavta --data-prefix -u -c10 -n5 -I -s 1280x800 --file=/tmp/frame-#.bin \ ++ -f SBGGR10 $(media-ctl -d $MDEV -e "Intel IPU6 ISYS Capture 0") ++ ++With the above command, 10 frames are captured at 1280x800 resolution with ++sBGGR10 format. The captured frames are available as /tmp/frame-#.bin files. ++ ++Here is another example of IPU6 ISYS RAW and metadata capture from camera ++sensor ov2740 on Lenovo X1 Yoga laptop. ++ ++.. code-block:: none ++ ++ media-ctl -l "\"ov2740 14-0036\":0 -> \"Intel IPU6 CSI2 1\":0[1]" ++ media-ctl -l "\"Intel IPU6 CSI2 1\":1 -> \"Intel IPU6 ISYS Capture 0\":0[5]" ++ media-ctl -l "\"Intel IPU6 CSI2 1\":2 -> \"Intel IPU6 ISYS Capture 1\":0[5]" ++ ++ # set routing ++ media-ctl -v -R "\"Intel IPU6 CSI2 1\" [0/0->1/0[1],0/1->2/1[1]]" ++ ++ media-ctl -v "\"Intel IPU6 CSI2 1\":0/0 [fmt:SGRBG10/1932x1092]" ++ media-ctl -v "\"Intel IPU6 CSI2 1\":0/1 [fmt:GENERIC_8/97x1]" ++ media-ctl -v "\"Intel IPU6 CSI2 1\":1/0 [fmt:SGRBG10/1932x1092]" ++ media-ctl -v "\"Intel IPU6 CSI2 1\":2/1 [fmt:GENERIC_8/97x1]" ++ ++ CAPTURE_DEV=$(media-ctl -e "Intel IPU6 ISYS Capture 0") ++ ./yavta --data-prefix -c100 -n5 -I -s1932x1092 --file=/tmp/frame-#.bin \ ++ -f SGRBG10 ${CAPTURE_DEV} ++ ++ CAPTURE_META=$(media-ctl -e "Intel IPU6 ISYS Capture 1") ++ ./yavta --data-prefix -c100 -n5 -I -s97x1 -B meta-capture \ ++ --file=/tmp/meta-#.bin -f GENERIC_8 ${CAPTURE_META} ++ ++References ++========== ++ ++.. [#f1] https://git.ideasonboard.org/?p=media-ctl.git;a=summary ++.. [#f2] https://git.ideasonboard.org/yavta.git +diff --git a/Documentation/admin-guide/media/ipu6_isys_graph.svg b/Documentation/admin-guide/media/ipu6_isys_graph.svg +new file mode 100644 +index 000000000000..707747c75280 +--- /dev/null ++++ b/Documentation/admin-guide/media/ipu6_isys_graph.svg +@@ -0,0 +1,174 @@ ++<?xml version="1.0" encoding="UTF-8" standalone="no"?> ++<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" ++ "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> ++<!-- Generated by graphviz version 2.38.0 (20140413.2041) ++ --> ++<!-- Title: board Pages: 1 --> ++<svg width="559pt" height="810pt" ++ viewBox="0.00 0.00 559.00 809.50" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> ++<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 805.5)"> ++<title>board</title> ++<polygon fill="white" stroke="none" points="-4,4 -4,-805.5 555,-805.5 555,4 -4,4"/> ++<!-- n00000001 --> ++<g id="node1" class="node"><title>n00000001</title> ++<polygon fill="#66cd00" stroke="black" points="551,-192.5 387,-192.5 387,-154.5 551,-154.5 551,-192.5"/> ++<text text-anchor="middle" x="469" y="-177.3" font-family="Times,serif" font-size="14.00">Intel IPU6 ISYS Capture 0</text> ++<text text-anchor="middle" x="469" y="-162.3" font-family="Times,serif" font-size="14.00">/dev/video0</text> ++</g> ++<!-- n00000002 --> ++<g id="node2" class="node"><title>n00000002</title> ++<polygon fill="#66cd00" stroke="black" points="551,-395.5 387,-395.5 387,-357.5 551,-357.5 551,-395.5"/> ++<text text-anchor="middle" x="469" y="-380.3" font-family="Times,serif" font-size="14.00">Intel IPU6 ISYS Capture 1</text> ++<text text-anchor="middle" x="469" y="-365.3" font-family="Times,serif" font-size="14.00">/dev/video1</text> ++</g> ++<!-- n00000003 --> ++<g id="node3" class="node"><title>n00000003</title> ++<polygon fill="#66cd00" stroke="black" points="551,-598.5 387,-598.5 387,-560.5 551,-560.5 551,-598.5"/> ++<text text-anchor="middle" x="469" y="-583.3" font-family="Times,serif" font-size="14.00">Intel IPU6 ISYS Capture 2</text> ++<text text-anchor="middle" x="469" y="-568.3" font-family="Times,serif" font-size="14.00">/dev/video2</text> ++</g> ++<!-- n00000004 --> ++<g id="node4" class="node"><title>n00000004</title> ++<polygon fill="#66cd00" stroke="black" points="551,-801.5 387,-801.5 387,-763.5 551,-763.5 551,-801.5"/> ++<text text-anchor="middle" x="469" y="-786.3" font-family="Times,serif" font-size="14.00">Intel IPU6 ISYS Capture 3</text> ++<text text-anchor="middle" x="469" y="-771.3" font-family="Times,serif" font-size="14.00">/dev/video3</text> ++</g> ++<!-- n0000007d --> ++<g id="node5" class="node"><title>n0000007d</title> ++<path fill="#ffb90f" stroke="black" d="M201,-0.5C201,-0.5 339,-0.5 339,-0.5 345,-0.5 351,-6.5 351,-12.5 351,-12.5 351,-172.5 351,-172.5 351,-178.5 345,-184.5 339,-184.5 339,-184.5 201,-184.5 201,-184.5 195,-184.5 189,-178.5 189,-172.5 189,-172.5 189,-12.5 189,-12.5 189,-6.5 195,-0.5 201,-0.5"/> ++<text text-anchor="middle" x="200.5" y="-88.8" font-family="Times,serif" font-size="14.00">0</text> ++<polyline fill="none" stroke="black" points="212,-0.5 212,-184.5 "/> ++<text text-anchor="middle" x="270" y="-96.3" font-family="Times,serif" font-size="14.00">Intel IPU6 CSI2 0</text> ++<text text-anchor="middle" x="270" y="-81.3" font-family="Times,serif" font-size="14.00">/dev/v4l-subdev0</text> ++<polyline fill="none" stroke="black" points="328,-0.5 328,-184.5 "/> ++<text text-anchor="middle" x="339.5" y="-169.3" font-family="Times,serif" font-size="14.00">1</text> ++<polyline fill="none" stroke="black" points="328,-161.5 351,-161.5 "/> ++<text text-anchor="middle" x="339.5" y="-146.3" font-family="Times,serif" font-size="14.00">2</text> ++<polyline fill="none" stroke="black" points="328,-138.5 351,-138.5 "/> ++<text text-anchor="middle" x="339.5" y="-123.3" font-family="Times,serif" font-size="14.00">3</text> ++<polyline fill="none" stroke="black" points="328,-115.5 351,-115.5 "/> ++<text text-anchor="middle" x="339.5" y="-100.3" font-family="Times,serif" font-size="14.00">4</text> ++<polyline fill="none" stroke="black" points="328,-92.5 351,-92.5 "/> ++<text text-anchor="middle" x="339.5" y="-77.3" font-family="Times,serif" font-size="14.00">5</text> ++<polyline fill="none" stroke="black" points="328,-69.5 351,-69.5 "/> ++<text text-anchor="middle" x="339.5" y="-54.3" font-family="Times,serif" font-size="14.00">6</text> ++<polyline fill="none" stroke="black" points="328,-46.5 351,-46.5 "/> ++<text text-anchor="middle" x="339.5" y="-31.3" font-family="Times,serif" font-size="14.00">7</text> ++<polyline fill="none" stroke="black" points="328,-23.5 351,-23.5 "/> ++<text text-anchor="middle" x="339.5" y="-8.3" font-family="Times,serif" font-size="14.00">8</text> ++</g> ++<!-- n0000007d->n00000001 --> ++<g id="edge1" class="edge"><title>n0000007d:port1->n00000001</title> ++<path fill="none" stroke="black" stroke-dasharray="5,2" d="M351,-173.5C359.322,-173.5 367.976,-173.5 376.644,-173.5"/> ++<polygon fill="black" stroke="black" points="376.807,-177 386.807,-173.5 376.807,-170 376.807,-177"/> ++</g> ++<!-- n00000087 --> ++<g id="node6" class="node"><title>n00000087</title> ++<path fill="#ffb90f" stroke="black" d="M201,-203.5C201,-203.5 339,-203.5 339,-203.5 345,-203.5 351,-209.5 351,-215.5 351,-215.5 351,-375.5 351,-375.5 351,-381.5 345,-387.5 339,-387.5 339,-387.5 201,-387.5 201,-387.5 195,-387.5 189,-381.5 189,-375.5 189,-375.5 189,-215.5 189,-215.5 189,-209.5 195,-203.5 201,-203.5"/> ++<text text-anchor="middle" x="200.5" y="-291.8" font-family="Times,serif" font-size="14.00">0</text> ++<polyline fill="none" stroke="black" points="212,-203.5 212,-387.5 "/> ++<text text-anchor="middle" x="270" y="-299.3" font-family="Times,serif" font-size="14.00">Intel IPU6 CSI2 1</text> ++<text text-anchor="middle" x="270" y="-284.3" font-family="Times,serif" font-size="14.00">/dev/v4l-subdev1</text> ++<polyline fill="none" stroke="black" points="328,-203.5 328,-387.5 "/> ++<text text-anchor="middle" x="339.5" y="-372.3" font-family="Times,serif" font-size="14.00">1</text> ++<polyline fill="none" stroke="black" points="328,-364.5 351,-364.5 "/> ++<text text-anchor="middle" x="339.5" y="-349.3" font-family="Times,serif" font-size="14.00">2</text> ++<polyline fill="none" stroke="black" points="328,-341.5 351,-341.5 "/> ++<text text-anchor="middle" x="339.5" y="-326.3" font-family="Times,serif" font-size="14.00">3</text> ++<polyline fill="none" stroke="black" points="328,-318.5 351,-318.5 "/> ++<text text-anchor="middle" x="339.5" y="-303.3" font-family="Times,serif" font-size="14.00">4</text> ++<polyline fill="none" stroke="black" points="328,-295.5 351,-295.5 "/> ++<text text-anchor="middle" x="339.5" y="-280.3" font-family="Times,serif" font-size="14.00">5</text> ++<polyline fill="none" stroke="black" points="328,-272.5 351,-272.5 "/> ++<text text-anchor="middle" x="339.5" y="-257.3" font-family="Times,serif" font-size="14.00">6</text> ++<polyline fill="none" stroke="black" points="328,-249.5 351,-249.5 "/> ++<text text-anchor="middle" x="339.5" y="-234.3" font-family="Times,serif" font-size="14.00">7</text> ++<polyline fill="none" stroke="black" points="328,-226.5 351,-226.5 "/> ++<text text-anchor="middle" x="339.5" y="-211.3" font-family="Times,serif" font-size="14.00">8</text> ++</g> ++<!-- n00000087->n00000002 --> ++<g id="edge2" class="edge"><title>n00000087:port1->n00000002</title> ++<path fill="none" stroke="black" stroke-dasharray="5,2" d="M351,-376.5C359.322,-376.5 367.976,-376.5 376.644,-376.5"/> ++<polygon fill="black" stroke="black" points="376.807,-380 386.807,-376.5 376.807,-373 376.807,-380"/> ++</g> ++<!-- n00000091 --> ++<g id="node7" class="node"><title>n00000091</title> ++<path fill="#ffb90f" stroke="black" d="M201,-406.5C201,-406.5 339,-406.5 339,-406.5 345,-406.5 351,-412.5 351,-418.5 351,-418.5 351,-578.5 351,-578.5 351,-584.5 345,-590.5 339,-590.5 339,-590.5 201,-590.5 201,-590.5 195,-590.5 189,-584.5 189,-578.5 189,-578.5 189,-418.5 189,-418.5 189,-412.5 195,-406.5 201,-406.5"/> ++<text text-anchor="middle" x="200.5" y="-494.8" font-family="Times,serif" font-size="14.00">0</text> ++<polyline fill="none" stroke="black" points="212,-406.5 212,-590.5 "/> ++<text text-anchor="middle" x="270" y="-502.3" font-family="Times,serif" font-size="14.00">Intel IPU6 CSI2 2</text> ++<text text-anchor="middle" x="270" y="-487.3" font-family="Times,serif" font-size="14.00">/dev/v4l-subdev2</text> ++<polyline fill="none" stroke="black" points="328,-406.5 328,-590.5 "/> ++<text text-anchor="middle" x="339.5" y="-575.3" font-family="Times,serif" font-size="14.00">1</text> ++<polyline fill="none" stroke="black" points="328,-567.5 351,-567.5 "/> ++<text text-anchor="middle" x="339.5" y="-552.3" font-family="Times,serif" font-size="14.00">2</text> ++<polyline fill="none" stroke="black" points="328,-544.5 351,-544.5 "/> ++<text text-anchor="middle" x="339.5" y="-529.3" font-family="Times,serif" font-size="14.00">3</text> ++<polyline fill="none" stroke="black" points="328,-521.5 351,-521.5 "/> ++<text text-anchor="middle" x="339.5" y="-506.3" font-family="Times,serif" font-size="14.00">4</text> ++<polyline fill="none" stroke="black" points="328,-498.5 351,-498.5 "/> ++<text text-anchor="middle" x="339.5" y="-483.3" font-family="Times,serif" font-size="14.00">5</text> ++<polyline fill="none" stroke="black" points="328,-475.5 351,-475.5 "/> ++<text text-anchor="middle" x="339.5" y="-460.3" font-family="Times,serif" font-size="14.00">6</text> ++<polyline fill="none" stroke="black" points="328,-452.5 351,-452.5 "/> ++<text text-anchor="middle" x="339.5" y="-437.3" font-family="Times,serif" font-size="14.00">7</text> ++<polyline fill="none" stroke="black" points="328,-429.5 351,-429.5 "/> ++<text text-anchor="middle" x="339.5" y="-414.3" font-family="Times,serif" font-size="14.00">8</text> ++</g> ++<!-- n00000091->n00000003 --> ++<g id="edge3" class="edge"><title>n00000091:port1->n00000003</title> ++<path fill="none" stroke="black" d="M351,-579.5C359.322,-579.5 367.976,-579.5 376.644,-579.5"/> ++<polygon fill="black" stroke="black" points="376.807,-583 386.807,-579.5 376.807,-576 376.807,-583"/> ++</g> ++<!-- n0000009b --> ++<g id="node8" class="node"><title>n0000009b</title> ++<path fill="#ffb90f" stroke="black" d="M201,-609.5C201,-609.5 339,-609.5 339,-609.5 345,-609.5 351,-615.5 351,-621.5 351,-621.5 351,-781.5 351,-781.5 351,-787.5 345,-793.5 339,-793.5 339,-793.5 201,-793.5 201,-793.5 195,-793.5 189,-787.5 189,-781.5 189,-781.5 189,-621.5 189,-621.5 189,-615.5 195,-609.5 201,-609.5"/> ++<text text-anchor="middle" x="200.5" y="-697.8" font-family="Times,serif" font-size="14.00">0</text> ++<polyline fill="none" stroke="black" points="212,-609.5 212,-793.5 "/> ++<text text-anchor="middle" x="270" y="-705.3" font-family="Times,serif" font-size="14.00">Intel IPU6 CSI2 3</text> ++<text text-anchor="middle" x="270" y="-690.3" font-family="Times,serif" font-size="14.00">/dev/v4l-subdev3</text> ++<polyline fill="none" stroke="black" points="328,-609.5 328,-793.5 "/> ++<text text-anchor="middle" x="339.5" y="-778.3" font-family="Times,serif" font-size="14.00">1</text> ++<polyline fill="none" stroke="black" points="328,-770.5 351,-770.5 "/> ++<text text-anchor="middle" x="339.5" y="-755.3" font-family="Times,serif" font-size="14.00">2</text> ++<polyline fill="none" stroke="black" points="328,-747.5 351,-747.5 "/> ++<text text-anchor="middle" x="339.5" y="-732.3" font-family="Times,serif" font-size="14.00">3</text> ++<polyline fill="none" stroke="black" points="328,-724.5 351,-724.5 "/> ++<text text-anchor="middle" x="339.5" y="-709.3" font-family="Times,serif" font-size="14.00">4</text> ++<polyline fill="none" stroke="black" points="328,-701.5 351,-701.5 "/> ++<text text-anchor="middle" x="339.5" y="-686.3" font-family="Times,serif" font-size="14.00">5</text> ++<polyline fill="none" stroke="black" points="328,-678.5 351,-678.5 "/> ++<text text-anchor="middle" x="339.5" y="-663.3" font-family="Times,serif" font-size="14.00">6</text> ++<polyline fill="none" stroke="black" points="328,-655.5 351,-655.5 "/> ++<text text-anchor="middle" x="339.5" y="-640.3" font-family="Times,serif" font-size="14.00">7</text> ++<polyline fill="none" stroke="black" points="328,-632.5 351,-632.5 "/> ++<text text-anchor="middle" x="339.5" y="-617.3" font-family="Times,serif" font-size="14.00">8</text> ++</g> ++<!-- n0000009b->n00000004 --> ++<g id="edge4" class="edge"><title>n0000009b:port1->n00000004</title> ++<path fill="none" stroke="black" stroke-dasharray="5,2" d="M351,-782.5C359.322,-782.5 367.976,-782.5 376.644,-782.5"/> ++<polygon fill="black" stroke="black" points="376.807,-786 386.807,-782.5 376.807,-779 376.807,-786"/> ++</g> ++<!-- n00000865 --> ++<g id="node9" class="node"><title>n00000865</title> ++<path fill="cornflowerblue" stroke="black" d="M12,-479.5C12,-479.5 141,-479.5 141,-479.5 147,-479.5 153,-485.5 153,-491.5 153,-491.5 153,-505.5 153,-505.5 153,-511.5 147,-517.5 141,-517.5 141,-517.5 12,-517.5 12,-517.5 6,-517.5 0,-511.5 0,-505.5 0,-505.5 0,-491.5 0,-491.5 0,-485.5 6,-479.5 12,-479.5"/> ++<text text-anchor="middle" x="10" y="-494.8" font-family="Times,serif" font-size="14.00"> </text> ++<polyline fill="none" stroke="black" points="20,-479.5 20,-517.5 "/> ++<text text-anchor="middle" x="75" y="-502.3" font-family="Times,serif" font-size="14.00">ov01a10 3-0036</text> ++<text text-anchor="middle" x="75" y="-487.3" font-family="Times,serif" font-size="14.00">/dev/v4l-subdev4</text> ++<polyline fill="none" stroke="black" points="130,-479.5 130,-517.5 "/> ++<text text-anchor="middle" x="141.5" y="-494.8" font-family="Times,serif" font-size="14.00">0</text> ++</g> ++<!-- n00000865->n00000091 --> ++<g id="edge5" class="edge"><title>n00000865:port0->n00000091:port0</title> ++<path fill="none" stroke="black" d="M153,-498.5C165,-498.5 170.25,-498.5 178.875,-498.5"/> ++<polygon fill="black" stroke="black" points="179,-502 189,-498.5 179,-495 179,-502"/> ++</g> ++<!-- n00000866 --> ++<!-- n00000866->n0000007d --> ++<!-- n00000867 --> ++<!-- n00000867->n00000087 --> ++<!-- n00000868 --> ++<!-- n00000868->n0000009b --> ++</g> ++</svg> +diff --git a/Documentation/admin-guide/media/v4l-drivers.rst b/Documentation/admin-guide/media/v4l-drivers.rst +index 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 <bingbu.cao@intel.com> +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 <bingbu.cao@intel.com> +Link: https://lore.kernel.org/r/20240111065531.2418836-16-bingbu.cao@intel.com +Signed-off-by: Hans de Goede <hdegoede@redhat.com> +--- + .../driver-api/media/drivers/index.rst | 1 + + .../driver-api/media/drivers/ipu6.rst | 205 ++++++++++++++++++ + 2 files changed, 206 insertions(+) + create mode 100644 Documentation/driver-api/media/drivers/ipu6.rst + +diff --git a/Documentation/driver-api/media/drivers/index.rst b/Documentation/driver-api/media/drivers/index.rst +index c4123a16b5f9..7f6f3dcd5c90 100644 +--- a/Documentation/driver-api/media/drivers/index.rst ++++ b/Documentation/driver-api/media/drivers/index.rst +@@ -26,6 +26,7 @@ Video4Linux (V4L) drivers + vimc-devel + zoran + ccs/ccs ++ ipu6 + + + Digital TV drivers +diff --git a/Documentation/driver-api/media/drivers/ipu6.rst b/Documentation/driver-api/media/drivers/ipu6.rst +new file mode 100644 +index 000000000000..b6357155c13b +--- /dev/null ++++ b/Documentation/driver-api/media/drivers/ipu6.rst +@@ -0,0 +1,205 @@ ++.. SPDX-License-Identifier: GPL-2.0 ++ ++================== ++Intel IPU6 Driver ++================== ++ ++Author: Bingbu Cao <bingbu.cao@intel.com> ++ ++Overview ++========= ++ ++Intel IPU6 is the sixth generation of Intel Image Processing Unit used in some ++Intel Chipsets such as Tiger Lake, Jasper Lake, Alder Lake, Raptor Lake and ++Meteor Lake. IPU6 consists of two major systems: Input System (IS) and ++Processing System (PS). IPU6 are visible on the PCI bus as a single device, ++it can be found by ``lspci``: ++ ++``0000:00:05.0 Multimedia controller: Intel Corporation Device xxxx (rev xx)`` ++ ++IPU6 has a 16 MB BAR in PCI configuration Space for MMIO registers which is ++visible for driver. ++ ++Buttress ++========= ++ ++The IPU6 is connecting to the system fabric with ``Buttress`` which is enabling ++host driver to control the IPU6, it also allows IPU6 access the system memory to ++store and load frame pixel streams and any other metadata. ++ ++``Buttress`` mainly manages several system functionalities - power management, ++interrupt handling, firmware authentication and global timer sync. ++ ++IS and PS Power flow ++--------------------------- ++ ++IPU6 driver initialize the IS and PS power up or down request by setting the ++Buttress frequency control register for IS and PS - ++``IPU6_BUTTRESS_REG_IS_FREQ_CTL`` and ``IPU6_BUTTRESS_REG_PS_FREQ_CTL`` in ++function: ++ ++.. c:function:: int ipu6_buttress_power(..., bool on) ++ ++Buttress forwards the request to Punit, after Punit execute the power up flow, ++buttress indicates driver that IS or PS is powered up by updating the power ++status registers. ++ ++.. Note:: IS power up needs take place prior to PS power up, IS power down needs ++ take place after PS power down due to hardware limitation. ++ ++ ++Interrupt ++------------ ++ ++IPU6 interrupt can be generated as MSI or INTA, interrupt will be triggered ++when IS, PS, Buttress event or error happen, driver can get the interrupt ++cause by reading the interrupt status register ``BUTTRESS_REG_ISR_STATUS``, ++driver firstly clear the irq status and then call specific IS or PS irq handler. ++ ++.. c:function:: irqreturn_t ipu6_buttress_isr(int irq, ...) ++ ++Security and firmware authentication ++------------------------------------- ++To address the IPU6 firmware security concerns, the IPU6 firmware needs to ++undergo an authentication process before it is allowed to executed on the IPU6 ++internal processors. Driver will work with Converged Security Engine (CSE) to ++complete authentication process. CSE is responsible of authenticating the ++IPU6 firmware, the authenticated firmware binary is copied into an isolated ++memory region. Firmware authentication process is implemented by CSE following ++an IPC handshake with driver. There are some Buttress registers used by CSE and ++driver to communicate with each other as IPC messages. ++ ++.. c:function:: int ipu6_buttress_authenticate(...) ++ ++Global timer sync ++------------------ ++IPU driver initiates a Hammock Harbor synchronization flow each time it starts ++camera operation. IPU will synchronizes an internal counter in the Buttress ++with a copy of SoC time, this counter keeps the updated time until camera ++operation is stopped. Driver can use this time counter to calibrate the ++timestamp based on the timestamp in response event from firmware. ++ ++.. c:function:: int ipu6_buttress_start_tsc_sync(...) ++ ++ ++DMA and MMU ++============ ++ ++IPU6 has its own scalar processor where the firmware run at, it has ++an internal 32-bits virtual address space. IPU6 has MMU address translation ++hardware to allow that scalar process access the internal memory and external ++system memory through IPU6 virtual address. The address translation is ++based on two levels of page lookup tables stored in system memory which are ++maintained by IPU6 driver. IPU6 driver sets the level-1 page table base address ++to MMU register and allow MMU to lookup the page table. ++ ++IPU6 driver exports its own DMA operations. Driver will update the page table ++entries for each DMA operation and invalidate the MMU TLB after each unmap and ++free. ++ ++.. code-block:: none ++ ++ const struct dma_map_ops ipu6_dma_ops = { ++ .alloc = ipu6_dma_alloc, ++ .free = ipu6_dma_free, ++ .mmap = ipu6_dma_mmap, ++ .map_sg = ipu6_dma_map_sg, ++ .unmap_sg = ipu6_dma_unmap_sg, ++ ... ++ }; ++ ++.. Note:: IPU6 MMU works behind IOMMU, so for each IPU6 DMA ops, driver will ++ call generic PCI DMA ops to ask IOMMU to do the additional mapping ++ if VT-d enabled. ++ ++ ++Firmware file format ++===================== ++ ++IPU6 release the firmware in Code Partition Directory (CPD) file format. The ++CPD firmware contains a CPD header, several CPD entries and CPD components. ++CPD component includes 3 entries - manifest, metadata and module data. Manifest ++and metadata are defined by CSE and used by CSE for authentication. Module data ++is defined by IPU6 which holds the binary data of firmware called package ++directory. IPU6 driver (``ipu6-cpd.c``) parses and validates the CPD firmware ++file and get the package directory binary data of IPU6 firmware, copy it to ++specific DMA buffer and sets its base address to Buttress ``FW_SOURCE_BASE`` ++register, CSE will do authentication for this firmware binary. ++ ++ ++Syscom interface ++================ ++ ++IPU6 driver communicates with firmware via syscom ABI. Syscom is an ++inter-processor communication mechanism between IPU scalar processor and CPU. ++There are a number of resources shared between firmware and software. ++A system memory region where the message queues reside, firmware can access the ++memory region via IPU MMU. Syscom queues are FIFO fixed depth queues with ++configurable elements ``token`` (message). There is also a common IPU MMIO ++registers where the queue read and write indices reside. Software and firmware ++work as producer and consumer of tokens in queue, and update the write and read ++indices separately when sending or receiving each message. ++ ++IPU6 driver must prepare and configure the number of input and output queues, ++configure the count of tokens per queue and the size of per token before ++initiate and start the communication with firmware, firmware and software must ++use same configurations. IPU6 Buttress has a number of firmware boot parameter ++registers which can be used to store the address of configuration and initiate ++the Syscom state, then driver can request firmware to start and run via setting ++the scalar processor control status register. ++ ++ ++Input System ++============== ++ ++IPU6 input system consists of MIPI D-PHY and several CSI receiver controllers, ++it can capture image pixel data from camera sensors or other MIPI CSI output ++devices. ++ ++D-PHYs and CSI-2 ports lane mapping ++----------------------------------- ++ ++IPU6 integrates different D-PHY IPs on different SoCs, on Tiger Lake and Alder ++Lake, IPU6 integrates MCD10 D-PHY, IPU6SE on Jasper Lake integrates JSL D-PHY ++and IPU6EP on Meteor Lake integrates a Synopsys DWC D-PHY. There is an adaption ++layer between D-PHY and CSI receiver controller which includes port ++configuration, PHY wrapper or private test interfaces for D-PHY. There are 3 ++D-PHY drivers ``ipu6-isys-mcd-phy.c``, ``ipu6-isys-jsl-phy.c`` and ++``ipu6-isys-dwc-phy.c`` program the above 3 D-PHYs in IPU6. ++ ++Different IPU6 version has different D-PHY lanes mappings, On Tiger Lake, there ++are 12 data lanes and 8 clock lanes, IPU6 support maximum 8 CSI-2 ports, see ++the ppi mmapping in ``ipu6-isys-mcd-phy.c`` for more information. On Jasper Lake ++and Alder Lake, D-PHY has 8 data lanes and 4 clock lanes, IPU6 support maximum 4 ++CSI-2 ports. For Meteor Lake, D-PHY has 12 data lanes and 6 clock lanes, IPU6 ++support maximum 6 CSI-2 ports. ++ ++.. Note:: Each adjacent CSI ports work as a pair and share the data lanes. ++ For example, for CSI port 0 and 1, CSI port 0 support maximum 4 ++ data lanes, CSI port 1 support maximum 2 data lanes, CSI port 0 ++ with 2 data lanes can work together with CSI port 1 with 2 data lanes. ++ If trying to use CSI port 0 with 4 lanes, CSI port 1 will not be ++ available as the 4 data lanes are shared by CSI port 0 and 1. Same ++ scenario is also applied for CSI port 2/3, 4/5 and 7/8. ++ ++IS firmware ABIs ++---------------- ++ ++IPU6 firmware define a series of ABIs to software. In general, software firstly ++prepare the stream configuration ``struct ipu6_fw_isys_stream_cfg_data_abi`` ++and send the configuration to firmware via sending ``STREAM_OPEN`` command. ++Stream configuration includes input pins and output pins, input pin ++``struct ipu6_fw_isys_input_pin_info_abi`` defines the resolution and data type ++of input source, output pin ``struct ipu6_fw_isys_output_pin_info_abi`` ++defines the output resolution, stride and frame format, etc. Once driver get the ++interrupt from firmware that indicates stream open successfully, driver will ++send the ``STREAM_START`` and ``STREAM_CAPTURE`` command to request firmware to ++start capturing image frames. ``STREAM_CAPTURE`` command queues the buffers to ++firmware with ``struct ipu6_fw_isys_frame_buff_set``, software then wait the ++interrupt and response from firmware, ``PIN_DATA_READY`` means data ready ++on specific output pin and then software return the buffers to user. ++ ++.. Note:: See :ref:`Examples<ipu6_isys_capture_examples>` about how to do ++ capture by IPU6 IS driver. ++ ++ +-- +2.43.0 + +From 7103b8fabe3158d203b72036abc2a964687d46b8 Mon Sep 17 00:00:00 2001 +From: Hans de Goede <hdegoede@redhat.com> +Date: Mon, 15 Jan 2024 15:57:06 +0100 +Subject: [PATCH 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 <hdegoede@redhat.com> +--- + .../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 <hdegoede@redhat.com> +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 <hdegoede@redhat.com> +--- + drivers/media/i2c/Kconfig | 9 + + drivers/media/i2c/Makefile | 1 + + drivers/media/i2c/ov01a1s.c | 1191 +++++++++++++++++++++++++++++++++++ + 3 files changed, 1201 insertions(+) + create mode 100644 drivers/media/i2c/ov01a1s.c + +diff --git a/drivers/media/i2c/Kconfig b/drivers/media/i2c/Kconfig +index 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 <asm/unaligned.h> ++#include <linux/acpi.h> ++#include <linux/delay.h> ++#include <linux/i2c.h> ++#include <linux/module.h> ++#include <linux/pm_runtime.h> ++#include <linux/version.h> ++#include <media/v4l2-ctrls.h> ++#include <media/v4l2-device.h> ++#include <media/v4l2-fwnode.h> ++#if IS_ENABLED(CONFIG_INTEL_SKL_INT3472) ++#include <linux/clk.h> ++#include <linux/gpio/consumer.h> ++#elif IS_ENABLED(CONFIG_POWER_CTRL_LOGIC) ++#include "power_ctrl_logic.h" ++#endif ++#if IS_ENABLED(CONFIG_INTEL_VSC) ++#include <linux/vsc.h> ++#endif ++ ++#define OV01A1S_LINK_FREQ_400MHZ 400000000ULL ++#define OV01A1S_SCLK 40000000LL ++#define OV01A1S_MCLK 19200000 ++#define OV01A1S_DATA_LANES 1 ++#define OV01A1S_RGB_DEPTH 10 ++ ++#define OV01A1S_REG_CHIP_ID 0x300a ++#define OV01A1S_CHIP_ID 0x560141 ++ ++#define OV01A1S_REG_MODE_SELECT 0x0100 ++#define OV01A1S_MODE_STANDBY 0x00 ++#define OV01A1S_MODE_STREAMING 0x01 ++ ++/* vertical-timings from sensor */ ++#define OV01A1S_REG_VTS 0x380e ++#define OV01A1S_VTS_DEF 0x0380 ++#define OV01A1S_VTS_MIN 0x0380 ++#define OV01A1S_VTS_MAX 0xffff ++ ++/* Exposure controls from sensor */ ++#define OV01A1S_REG_EXPOSURE 0x3501 ++#define OV01A1S_EXPOSURE_MIN 4 ++#define OV01A1S_EXPOSURE_MAX_MARGIN 8 ++#define OV01A1S_EXPOSURE_STEP 1 ++ ++/* Analog gain controls from sensor */ ++#define OV01A1S_REG_ANALOG_GAIN 0x3508 ++#define OV01A1S_ANAL_GAIN_MIN 0x100 ++#define OV01A1S_ANAL_GAIN_MAX 0xffff ++#define OV01A1S_ANAL_GAIN_STEP 1 ++ ++/* Digital gain controls from sensor */ ++#define OV01A1S_REG_DIGILAL_GAIN_B 0x350A ++#define OV01A1S_REG_DIGITAL_GAIN_GB 0x3510 ++#define OV01A1S_REG_DIGITAL_GAIN_GR 0x3513 ++#define OV01A1S_REG_DIGITAL_GAIN_R 0x3516 ++#define OV01A1S_DGTL_GAIN_MIN 0 ++#define OV01A1S_DGTL_GAIN_MAX 0x3ffff ++#define OV01A1S_DGTL_GAIN_STEP 1 ++#define OV01A1S_DGTL_GAIN_DEFAULT 1024 ++ ++/* Test Pattern Control */ ++#define OV01A1S_REG_TEST_PATTERN 0x4503 ++#define OV01A1S_TEST_PATTERN_ENABLE BIT(7) ++#define OV01A1S_TEST_PATTERN_BAR_SHIFT 0 ++ ++enum { ++ OV01A1S_LINK_FREQ_400MHZ_INDEX, ++}; ++ ++struct ov01a1s_reg { ++ u16 address; ++ u8 val; ++}; ++ ++struct ov01a1s_reg_list { ++ u32 num_of_regs; ++ const struct ov01a1s_reg *regs; ++}; ++ ++struct ov01a1s_link_freq_config { ++ const struct ov01a1s_reg_list reg_list; ++}; ++ ++struct ov01a1s_mode { ++ /* Frame width in pixels */ ++ u32 width; ++ ++ /* Frame height in pixels */ ++ u32 height; ++ ++ /* Horizontal timining size */ ++ u32 hts; ++ ++ /* Default vertical timining size */ ++ u32 vts_def; ++ ++ /* Min vertical timining size */ ++ u32 vts_min; ++ ++ /* Link frequency needed for this resolution */ ++ u32 link_freq_index; ++ ++ /* Sensor register settings for this resolution */ ++ const struct ov01a1s_reg_list reg_list; ++}; ++ ++static const struct ov01a1s_reg mipi_data_rate_720mbps[] = { ++}; ++ ++static const struct ov01a1s_reg sensor_1296x800_setting[] = { ++ {0x0103, 0x01}, ++ {0x0302, 0x00}, ++ {0x0303, 0x06}, ++ {0x0304, 0x01}, ++ {0x0305, 0x90}, ++ {0x0306, 0x00}, ++ {0x0308, 0x01}, ++ {0x0309, 0x00}, ++ {0x030c, 0x01}, ++ {0x0322, 0x01}, ++ {0x0323, 0x06}, ++ {0x0324, 0x01}, ++ {0x0325, 0x68}, ++ {0x3002, 0xa1}, ++ {0x301e, 0xf0}, ++ {0x3022, 0x01}, ++ {0x3501, 0x03}, ++ {0x3502, 0x78}, ++ {0x3504, 0x0c}, ++ {0x3508, 0x01}, ++ {0x3509, 0x00}, ++ {0x3601, 0xc0}, ++ {0x3603, 0x71}, ++ {0x3610, 0x68}, ++ {0x3611, 0x86}, ++ {0x3640, 0x10}, ++ {0x3641, 0x80}, ++ {0x3642, 0xdc}, ++ {0x3646, 0x55}, ++ {0x3647, 0x57}, ++ {0x364b, 0x00}, ++ {0x3653, 0x10}, ++ {0x3655, 0x00}, ++ {0x3656, 0x00}, ++ {0x365f, 0x0f}, ++ {0x3661, 0x45}, ++ {0x3662, 0x24}, ++ {0x3663, 0x11}, ++ {0x3664, 0x07}, ++ {0x3709, 0x34}, ++ {0x370b, 0x6f}, ++ {0x3714, 0x22}, ++ {0x371b, 0x27}, ++ {0x371c, 0x67}, ++ {0x371d, 0xa7}, ++ {0x371e, 0xe7}, ++ {0x3730, 0x81}, ++ {0x3733, 0x10}, ++ {0x3734, 0x40}, ++ {0x3737, 0x04}, ++ {0x3739, 0x1c}, ++ {0x3767, 0x00}, ++ {0x376c, 0x81}, ++ {0x3772, 0x14}, ++ {0x37c2, 0x04}, ++ {0x37d8, 0x03}, ++ {0x37d9, 0x0c}, ++ {0x37e0, 0x00}, ++ {0x37e1, 0x08}, ++ {0x37e2, 0x10}, ++ {0x37e3, 0x04}, ++ {0x37e4, 0x04}, ++ {0x37e5, 0x03}, ++ {0x37e6, 0x04}, ++ {0x3800, 0x00}, ++ {0x3801, 0x00}, ++ {0x3802, 0x00}, ++ {0x3803, 0x00}, ++ {0x3804, 0x05}, ++ {0x3805, 0x0f}, ++ {0x3806, 0x03}, ++ {0x3807, 0x2f}, ++ {0x3808, 0x05}, ++ {0x3809, 0x00}, ++ {0x380a, 0x03}, ++ {0x380b, 0x1e}, ++ {0x380c, 0x05}, ++ {0x380d, 0xd0}, ++ {0x380e, 0x03}, ++ {0x380f, 0x80}, ++ {0x3810, 0x00}, ++ {0x3811, 0x09}, ++ {0x3812, 0x00}, ++ {0x3813, 0x08}, ++ {0x3814, 0x01}, ++ {0x3815, 0x01}, ++ {0x3816, 0x01}, ++ {0x3817, 0x01}, ++ {0x3820, 0xa8}, ++ {0x3822, 0x03}, ++ {0x3832, 0x28}, ++ {0x3833, 0x10}, ++ {0x3b00, 0x00}, ++ {0x3c80, 0x00}, ++ {0x3c88, 0x02}, ++ {0x3c8c, 0x07}, ++ {0x3c8d, 0x40}, ++ {0x3cc7, 0x80}, ++ {0x4000, 0xc3}, ++ {0x4001, 0xe0}, ++ {0x4003, 0x40}, ++ {0x4008, 0x02}, ++ {0x4009, 0x19}, ++ {0x400a, 0x01}, ++ {0x400b, 0x6c}, ++ {0x4011, 0x00}, ++ {0x4041, 0x00}, ++ {0x4300, 0xff}, ++ {0x4301, 0x00}, ++ {0x4302, 0x0f}, ++ {0x4503, 0x00}, ++ {0x4601, 0x50}, ++ {0x481f, 0x34}, ++ {0x4825, 0x33}, ++ {0x4837, 0x14}, ++ {0x4881, 0x40}, ++ {0x4883, 0x01}, ++ {0x4890, 0x00}, ++ {0x4901, 0x00}, ++ {0x4902, 0x00}, ++ {0x4b00, 0x2a}, ++ {0x4b0d, 0x00}, ++ {0x450a, 0x04}, ++ {0x450b, 0x00}, ++ {0x5000, 0x65}, ++ {0x5004, 0x00}, ++ {0x5080, 0x40}, ++ {0x5200, 0x18}, ++ {0x4837, 0x14}, ++ {0x0305, 0xf4}, ++ {0x0325, 0xc2}, ++ {0x3808, 0x05}, ++ {0x3809, 0x10}, ++ {0x380a, 0x03}, ++ {0x380b, 0x1e}, ++ {0x3810, 0x00}, ++ {0x3811, 0x00}, ++ {0x3812, 0x00}, ++ {0x3813, 0x09}, ++ {0x3820, 0x88}, ++ {0x373d, 0x24}, ++}; ++ ++static const char * const ov01a1s_test_pattern_menu[] = { ++ "Disabled", ++ "Color Bar", ++ "Top-Bottom Darker Color Bar", ++ "Right-Left Darker Color Bar", ++ "Color Bar type 4", ++}; ++ ++static const s64 link_freq_menu_items[] = { ++ OV01A1S_LINK_FREQ_400MHZ, ++}; ++ ++static const struct ov01a1s_link_freq_config link_freq_configs[] = { ++ [OV01A1S_LINK_FREQ_400MHZ_INDEX] = { ++ .reg_list = { ++ .num_of_regs = ARRAY_SIZE(mipi_data_rate_720mbps), ++ .regs = mipi_data_rate_720mbps, ++ } ++ }, ++}; ++ ++static const struct ov01a1s_mode supported_modes[] = { ++ { ++ .width = 1296, ++ .height = 798, ++ .hts = 1488, ++ .vts_def = OV01A1S_VTS_DEF, ++ .vts_min = OV01A1S_VTS_MIN, ++ .reg_list = { ++ .num_of_regs = ARRAY_SIZE(sensor_1296x800_setting), ++ .regs = sensor_1296x800_setting, ++ }, ++ .link_freq_index = OV01A1S_LINK_FREQ_400MHZ_INDEX, ++ }, ++}; ++ ++struct ov01a1s { ++ struct v4l2_subdev sd; ++ struct media_pad pad; ++ struct v4l2_ctrl_handler ctrl_handler; ++ ++ /* V4L2 Controls */ ++ struct v4l2_ctrl *link_freq; ++ struct v4l2_ctrl *pixel_rate; ++ struct v4l2_ctrl *vblank; ++ struct v4l2_ctrl *hblank; ++ struct v4l2_ctrl *exposure; ++#if IS_ENABLED(CONFIG_INTEL_VSC) ++ struct v4l2_ctrl *privacy_status; ++ ++ /* VSC settings */ ++ struct vsc_mipi_config conf; ++ struct vsc_camera_status status; ++#endif ++ ++ /* Current mode */ ++ const struct ov01a1s_mode *cur_mode; ++ ++ /* To serialize asynchronus callbacks */ ++ struct mutex mutex; ++ ++ /* i2c client */ ++ struct i2c_client *client; ++ ++#if IS_ENABLED(CONFIG_INTEL_SKL_INT3472) ++ /* GPIO for reset */ ++ struct gpio_desc *reset_gpio; ++ /* GPIO for powerdown */ ++ struct gpio_desc *powerdown_gpio; ++ /* Power enable */ ++ struct regulator *avdd; ++ /* Clock provider */ ++ struct clk *clk; ++#endif ++ ++ enum { ++ OV01A1S_USE_DEFAULT = 0, ++#if IS_ENABLED(CONFIG_INTEL_SKL_INT3472) || IS_ENABLED(CONFIG_POWER_CTRL_LOGIC) ++ OV01A1S_USE_INT3472 = 1, ++#endif ++#if IS_ENABLED(CONFIG_INTEL_VSC) ++ OV01A1S_USE_INTEL_VSC = 2, ++#endif ++ } power_type; ++ ++ /* Streaming on/off */ ++ bool streaming; ++}; ++ ++static inline struct ov01a1s *to_ov01a1s(struct v4l2_subdev *subdev) ++{ ++ return container_of(subdev, struct ov01a1s, sd); ++} ++ ++static int ov01a1s_read_reg(struct ov01a1s *ov01a1s, u16 reg, u16 len, u32 *val) ++{ ++ struct i2c_client *client = ov01a1s->client; ++ struct i2c_msg msgs[2]; ++ u8 addr_buf[2]; ++ u8 data_buf[4] = {0}; ++ int ret = 0; ++ ++ if (len > sizeof(data_buf)) ++ return -EINVAL; ++ ++ put_unaligned_be16(reg, addr_buf); ++ msgs[0].addr = client->addr; ++ msgs[0].flags = 0; ++ msgs[0].len = sizeof(addr_buf); ++ msgs[0].buf = addr_buf; ++ msgs[1].addr = client->addr; ++ msgs[1].flags = I2C_M_RD; ++ msgs[1].len = len; ++ msgs[1].buf = &data_buf[sizeof(data_buf) - len]; ++ ++ ret = i2c_transfer(client->adapter, msgs, ARRAY_SIZE(msgs)); ++ if (ret != ARRAY_SIZE(msgs)) ++ return ret < 0 ? ret : -EIO; ++ ++ *val = get_unaligned_be32(data_buf); ++ ++ return 0; ++} ++ ++static int ov01a1s_write_reg(struct ov01a1s *ov01a1s, u16 reg, u16 len, u32 val) ++{ ++ struct i2c_client *client = ov01a1s->client; ++ u8 buf[6]; ++ int ret = 0; ++ ++ if (len > 4) ++ return -EINVAL; ++ ++ put_unaligned_be16(reg, buf); ++ put_unaligned_be32(val << 8 * (4 - len), buf + 2); ++ ++ ret = i2c_master_send(client, buf, len + 2); ++ if (ret != len + 2) ++ return ret < 0 ? ret : -EIO; ++ ++ return 0; ++} ++ ++static int ov01a1s_write_reg_list(struct ov01a1s *ov01a1s, ++ const struct ov01a1s_reg_list *r_list) ++{ ++ struct i2c_client *client = ov01a1s->client; ++ unsigned int i; ++ int ret = 0; ++ ++ for (i = 0; i < r_list->num_of_regs; i++) { ++ ret = ov01a1s_write_reg(ov01a1s, r_list->regs[i].address, 1, ++ r_list->regs[i].val); ++ if (ret) { ++ dev_err_ratelimited(&client->dev, ++ "write reg 0x%4.4x return err = %d", ++ r_list->regs[i].address, ret); ++ return ret; ++ } ++ } ++ ++ return 0; ++} ++ ++static int ov01a1s_update_digital_gain(struct ov01a1s *ov01a1s, u32 d_gain) ++{ ++ struct i2c_client *client = ov01a1s->client; ++ u32 real = d_gain << 6; ++ int ret = 0; ++ ++ ret = ov01a1s_write_reg(ov01a1s, OV01A1S_REG_DIGILAL_GAIN_B, 3, real); ++ if (ret) { ++ dev_err(&client->dev, "failed to set OV01A1S_REG_DIGITAL_GAIN_B"); ++ return ret; ++ } ++ ret = ov01a1s_write_reg(ov01a1s, OV01A1S_REG_DIGITAL_GAIN_GB, 3, real); ++ if (ret) { ++ dev_err(&client->dev, "failed to set OV01A1S_REG_DIGITAL_GAIN_GB"); ++ return ret; ++ } ++ ret = ov01a1s_write_reg(ov01a1s, OV01A1S_REG_DIGITAL_GAIN_GR, 3, real); ++ if (ret) { ++ dev_err(&client->dev, "failed to set OV01A1S_REG_DIGITAL_GAIN_GR"); ++ return ret; ++ } ++ ++ ret = ov01a1s_write_reg(ov01a1s, OV01A1S_REG_DIGITAL_GAIN_R, 3, real); ++ if (ret) { ++ dev_err(&client->dev, "failed to set OV01A1S_REG_DIGITAL_GAIN_R"); ++ return ret; ++ } ++ return ret; ++} ++ ++static int ov01a1s_test_pattern(struct ov01a1s *ov01a1s, u32 pattern) ++{ ++ if (pattern) ++ pattern = (pattern - 1) << OV01A1S_TEST_PATTERN_BAR_SHIFT | ++ OV01A1S_TEST_PATTERN_ENABLE; ++ ++ return ov01a1s_write_reg(ov01a1s, OV01A1S_REG_TEST_PATTERN, 1, pattern); ++} ++ ++static int ov01a1s_set_ctrl(struct v4l2_ctrl *ctrl) ++{ ++ struct ov01a1s *ov01a1s = container_of(ctrl->handler, ++ struct ov01a1s, ctrl_handler); ++ struct i2c_client *client = ov01a1s->client; ++ s64 exposure_max; ++ int ret = 0; ++ ++ /* Propagate change of current control to all related controls */ ++ if (ctrl->id == V4L2_CID_VBLANK) { ++ /* Update max exposure while meeting expected vblanking */ ++ exposure_max = ov01a1s->cur_mode->height + ctrl->val - ++ OV01A1S_EXPOSURE_MAX_MARGIN; ++ __v4l2_ctrl_modify_range(ov01a1s->exposure, ++ ov01a1s->exposure->minimum, ++ exposure_max, ov01a1s->exposure->step, ++ exposure_max); ++ } ++ ++ /* V4L2 controls values will be applied only when power is already up */ ++ if (!pm_runtime_get_if_in_use(&client->dev)) ++ return 0; ++ ++ switch (ctrl->id) { ++ case V4L2_CID_ANALOGUE_GAIN: ++ ret = ov01a1s_write_reg(ov01a1s, OV01A1S_REG_ANALOG_GAIN, 2, ++ ctrl->val); ++ break; ++ ++ case V4L2_CID_DIGITAL_GAIN: ++ ret = ov01a1s_update_digital_gain(ov01a1s, ctrl->val); ++ break; ++ ++ case V4L2_CID_EXPOSURE: ++ ret = ov01a1s_write_reg(ov01a1s, OV01A1S_REG_EXPOSURE, 2, ++ ctrl->val); ++ break; ++ ++ case V4L2_CID_VBLANK: ++ ret = ov01a1s_write_reg(ov01a1s, OV01A1S_REG_VTS, 2, ++ ov01a1s->cur_mode->height + ctrl->val); ++ break; ++ ++ case V4L2_CID_TEST_PATTERN: ++ ret = ov01a1s_test_pattern(ov01a1s, ctrl->val); ++ break; ++ ++#if IS_ENABLED(CONFIG_INTEL_VSC) ++ case V4L2_CID_PRIVACY: ++ dev_dbg(&client->dev, "set privacy to %d", ctrl->val); ++ break; ++ ++#endif ++ default: ++ ret = -EINVAL; ++ break; ++ } ++ ++ pm_runtime_put(&client->dev); ++ ++ return ret; ++} ++ ++static const struct v4l2_ctrl_ops ov01a1s_ctrl_ops = { ++ .s_ctrl = ov01a1s_set_ctrl, ++}; ++ ++static int ov01a1s_init_controls(struct ov01a1s *ov01a1s) ++{ ++ struct v4l2_ctrl_handler *ctrl_hdlr; ++ const struct ov01a1s_mode *cur_mode; ++ s64 exposure_max, h_blank; ++ u32 vblank_min, vblank_max, vblank_default; ++ int size; ++ int ret = 0; ++ ++ ctrl_hdlr = &ov01a1s->ctrl_handler; ++#if IS_ENABLED(CONFIG_INTEL_VSC) ++ ret = v4l2_ctrl_handler_init(ctrl_hdlr, 9); ++#else ++ ret = v4l2_ctrl_handler_init(ctrl_hdlr, 8); ++#endif ++ if (ret) ++ return ret; ++ ++ ctrl_hdlr->lock = &ov01a1s->mutex; ++ cur_mode = ov01a1s->cur_mode; ++ size = ARRAY_SIZE(link_freq_menu_items); ++ ++ ov01a1s->link_freq = v4l2_ctrl_new_int_menu(ctrl_hdlr, ++ &ov01a1s_ctrl_ops, ++ V4L2_CID_LINK_FREQ, ++ size - 1, 0, ++ link_freq_menu_items); ++ if (ov01a1s->link_freq) ++ ov01a1s->link_freq->flags |= V4L2_CTRL_FLAG_READ_ONLY; ++ ++ ov01a1s->pixel_rate = v4l2_ctrl_new_std(ctrl_hdlr, &ov01a1s_ctrl_ops, ++ V4L2_CID_PIXEL_RATE, 0, ++ OV01A1S_SCLK, 1, OV01A1S_SCLK); ++ ++ vblank_min = cur_mode->vts_min - cur_mode->height; ++ vblank_max = OV01A1S_VTS_MAX - cur_mode->height; ++ vblank_default = cur_mode->vts_def - cur_mode->height; ++ ov01a1s->vblank = v4l2_ctrl_new_std(ctrl_hdlr, &ov01a1s_ctrl_ops, ++ V4L2_CID_VBLANK, vblank_min, ++ vblank_max, 1, vblank_default); ++ ++ h_blank = cur_mode->hts - cur_mode->width; ++ ov01a1s->hblank = v4l2_ctrl_new_std(ctrl_hdlr, &ov01a1s_ctrl_ops, ++ V4L2_CID_HBLANK, h_blank, h_blank, ++ 1, h_blank); ++ if (ov01a1s->hblank) ++ ov01a1s->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY; ++#if IS_ENABLED(CONFIG_INTEL_VSC) ++ ov01a1s->privacy_status = v4l2_ctrl_new_std(ctrl_hdlr, ++ &ov01a1s_ctrl_ops, ++ V4L2_CID_PRIVACY, ++ 0, 1, 1, 0); ++#endif ++ ++ v4l2_ctrl_new_std(ctrl_hdlr, &ov01a1s_ctrl_ops, V4L2_CID_ANALOGUE_GAIN, ++ OV01A1S_ANAL_GAIN_MIN, OV01A1S_ANAL_GAIN_MAX, ++ OV01A1S_ANAL_GAIN_STEP, OV01A1S_ANAL_GAIN_MIN); ++ v4l2_ctrl_new_std(ctrl_hdlr, &ov01a1s_ctrl_ops, V4L2_CID_DIGITAL_GAIN, ++ OV01A1S_DGTL_GAIN_MIN, OV01A1S_DGTL_GAIN_MAX, ++ OV01A1S_DGTL_GAIN_STEP, OV01A1S_DGTL_GAIN_DEFAULT); ++ exposure_max = cur_mode->vts_def - OV01A1S_EXPOSURE_MAX_MARGIN; ++ ov01a1s->exposure = v4l2_ctrl_new_std(ctrl_hdlr, &ov01a1s_ctrl_ops, ++ V4L2_CID_EXPOSURE, ++ OV01A1S_EXPOSURE_MIN, ++ exposure_max, ++ OV01A1S_EXPOSURE_STEP, ++ exposure_max); ++ v4l2_ctrl_new_std_menu_items(ctrl_hdlr, &ov01a1s_ctrl_ops, ++ V4L2_CID_TEST_PATTERN, ++ ARRAY_SIZE(ov01a1s_test_pattern_menu) - 1, ++ 0, 0, ov01a1s_test_pattern_menu); ++ if (ctrl_hdlr->error) ++ return ctrl_hdlr->error; ++ ++ ov01a1s->sd.ctrl_handler = ctrl_hdlr; ++ ++ return 0; ++} ++ ++static void ov01a1s_update_pad_format(const struct ov01a1s_mode *mode, ++ struct v4l2_mbus_framefmt *fmt) ++{ ++ fmt->width = mode->width; ++ fmt->height = mode->height; ++ fmt->code = MEDIA_BUS_FMT_SGRBG10_1X10; ++ fmt->field = V4L2_FIELD_NONE; ++} ++ ++#if IS_ENABLED(CONFIG_INTEL_VSC) ++static void ov01a1s_vsc_privacy_callback(void *handle, ++ enum vsc_privacy_status status) ++{ ++ struct ov01a1s *ov01a1s = handle; ++ ++ v4l2_ctrl_s_ctrl(ov01a1s->privacy_status, !status); ++} ++ ++#endif ++static int ov01a1s_start_streaming(struct ov01a1s *ov01a1s) ++{ ++ struct i2c_client *client = ov01a1s->client; ++ const struct ov01a1s_reg_list *reg_list; ++ int link_freq_index; ++ int ret = 0; ++ ++ link_freq_index = ov01a1s->cur_mode->link_freq_index; ++ reg_list = &link_freq_configs[link_freq_index].reg_list; ++ ret = ov01a1s_write_reg_list(ov01a1s, reg_list); ++ if (ret) { ++ dev_err(&client->dev, "failed to set plls"); ++ return ret; ++ } ++ ++ reg_list = &ov01a1s->cur_mode->reg_list; ++ ret = ov01a1s_write_reg_list(ov01a1s, reg_list); ++ if (ret) { ++ dev_err(&client->dev, "failed to set mode"); ++ return ret; ++ } ++ ++ ret = __v4l2_ctrl_handler_setup(ov01a1s->sd.ctrl_handler); ++ if (ret) ++ return ret; ++ ++ ret = ov01a1s_write_reg(ov01a1s, OV01A1S_REG_MODE_SELECT, 1, ++ OV01A1S_MODE_STREAMING); ++ if (ret) ++ dev_err(&client->dev, "failed to start streaming"); ++ ++ return ret; ++} ++ ++static void ov01a1s_stop_streaming(struct ov01a1s *ov01a1s) ++{ ++ struct i2c_client *client = ov01a1s->client; ++ int ret = 0; ++ ++ ret = ov01a1s_write_reg(ov01a1s, OV01A1S_REG_MODE_SELECT, 1, ++ OV01A1S_MODE_STANDBY); ++ if (ret) ++ dev_err(&client->dev, "failed to stop streaming"); ++} ++ ++static int ov01a1s_set_stream(struct v4l2_subdev *sd, int enable) ++{ ++ struct ov01a1s *ov01a1s = to_ov01a1s(sd); ++ struct i2c_client *client = ov01a1s->client; ++ int ret = 0; ++ ++ if (ov01a1s->streaming == enable) ++ return 0; ++ ++ mutex_lock(&ov01a1s->mutex); ++ if (enable) { ++ ret = pm_runtime_get_sync(&client->dev); ++ if (ret < 0) { ++ pm_runtime_put_noidle(&client->dev); ++ mutex_unlock(&ov01a1s->mutex); ++ return ret; ++ } ++ ++ ret = ov01a1s_start_streaming(ov01a1s); ++ if (ret) { ++ enable = 0; ++ ov01a1s_stop_streaming(ov01a1s); ++ pm_runtime_put(&client->dev); ++ } ++ } else { ++ ov01a1s_stop_streaming(ov01a1s); ++ pm_runtime_put(&client->dev); ++ } ++ ++ ov01a1s->streaming = enable; ++ mutex_unlock(&ov01a1s->mutex); ++ ++ return ret; ++} ++ ++static int ov01a1s_power_off(struct device *dev) ++{ ++ struct v4l2_subdev *sd = dev_get_drvdata(dev); ++ struct ov01a1s *ov01a1s = to_ov01a1s(sd); ++ int ret = 0; ++ ++#if IS_ENABLED(CONFIG_INTEL_SKL_INT3472) ++ if (ov01a1s->power_type == OV01A1S_USE_INT3472) { ++ gpiod_set_value_cansleep(ov01a1s->reset_gpio, 1); ++ gpiod_set_value_cansleep(ov01a1s->powerdown_gpio, 1); ++ if (ov01a1s->avdd) ++ ret = regulator_disable(ov01a1s->avdd); ++ clk_disable_unprepare(ov01a1s->clk); ++ msleep(20); ++ } ++#elif IS_ENABLED(CONFIG_POWER_CTRL_LOGIC) ++ if (ov01a1s->power_type == OV01A1S_USE_INT3472) ++ ret = power_ctrl_logic_set_power(0); ++#endif ++#if IS_ENABLED(CONFIG_INTEL_VSC) ++ if (ov01a1s->power_type == OV01A1S_USE_INTEL_VSC) { ++ ret = vsc_release_camera_sensor(&ov01a1s->status); ++ if (ret && ret != -EAGAIN) ++ dev_err(dev, "Release VSC failed"); ++ } ++#endif ++ ++ return ret; ++} ++ ++static int ov01a1s_power_on(struct device *dev) ++{ ++ struct v4l2_subdev *sd = dev_get_drvdata(dev); ++ struct ov01a1s *ov01a1s = to_ov01a1s(sd); ++ int ret = 0; ++ ++#if IS_ENABLED(CONFIG_INTEL_SKL_INT3472) ++ if (ov01a1s->power_type == OV01A1S_USE_INT3472) { ++ ret = clk_prepare_enable(ov01a1s->clk); ++ if (ret) ++ return ret; ++ if (ov01a1s->avdd) ++ ret = regulator_enable(ov01a1s->avdd); ++ if (ret) ++ return ret; ++ gpiod_set_value_cansleep(ov01a1s->powerdown_gpio, 0); ++ gpiod_set_value_cansleep(ov01a1s->reset_gpio, 0); ++ msleep(20); ++ } ++#elif IS_ENABLED(CONFIG_POWER_CTRL_LOGIC) ++ if (ov01a1s->power_type == OV01A1S_USE_INT3472) ++ ret = power_ctrl_logic_set_power(1); ++#endif ++#if IS_ENABLED(CONFIG_INTEL_VSC) ++ if (ov01a1s->power_type == OV01A1S_USE_INTEL_VSC) { ++ ret = vsc_acquire_camera_sensor(&ov01a1s->conf, ++ ov01a1s_vsc_privacy_callback, ++ ov01a1s, &ov01a1s->status); ++ if (ret && ret != -EAGAIN) { ++ dev_err(dev, "Acquire VSC failed"); ++ return ret; ++ } ++ __v4l2_ctrl_s_ctrl(ov01a1s->privacy_status, ++ !(ov01a1s->status.status)); ++ } ++#endif ++ ++ return ret; ++} ++ ++static int __maybe_unused ov01a1s_suspend(struct device *dev) ++{ ++ struct i2c_client *client = to_i2c_client(dev); ++ struct v4l2_subdev *sd = i2c_get_clientdata(client); ++ struct ov01a1s *ov01a1s = to_ov01a1s(sd); ++ ++ mutex_lock(&ov01a1s->mutex); ++ if (ov01a1s->streaming) ++ ov01a1s_stop_streaming(ov01a1s); ++ ++ mutex_unlock(&ov01a1s->mutex); ++ ++ return 0; ++} ++ ++static int __maybe_unused ov01a1s_resume(struct device *dev) ++{ ++ struct i2c_client *client = to_i2c_client(dev); ++ struct v4l2_subdev *sd = i2c_get_clientdata(client); ++ struct ov01a1s *ov01a1s = to_ov01a1s(sd); ++ int ret = 0; ++ ++ mutex_lock(&ov01a1s->mutex); ++ if (!ov01a1s->streaming) ++ goto exit; ++ ++ ret = ov01a1s_start_streaming(ov01a1s); ++ if (ret) { ++ ov01a1s->streaming = false; ++ ov01a1s_stop_streaming(ov01a1s); ++ } ++ ++exit: ++ mutex_unlock(&ov01a1s->mutex); ++ return ret; ++} ++ ++static int ov01a1s_set_format(struct v4l2_subdev *sd, ++#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 14, 0) ++ struct v4l2_subdev_pad_config *cfg, ++#else ++ struct v4l2_subdev_state *sd_state, ++#endif ++ struct v4l2_subdev_format *fmt) ++{ ++ struct ov01a1s *ov01a1s = to_ov01a1s(sd); ++ const struct ov01a1s_mode *mode; ++ s32 vblank_def, h_blank; ++ ++ mode = v4l2_find_nearest_size(supported_modes, ++ ARRAY_SIZE(supported_modes), width, ++ height, fmt->format.width, ++ fmt->format.height); ++ ++ mutex_lock(&ov01a1s->mutex); ++ ov01a1s_update_pad_format(mode, &fmt->format); ++ if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) { ++#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 14, 0) ++ *v4l2_subdev_get_try_format(sd, cfg, fmt->pad) = fmt->format; ++#else ++ *v4l2_subdev_get_try_format(sd, sd_state, fmt->pad) = fmt->format; ++#endif ++ } else { ++ ov01a1s->cur_mode = mode; ++ __v4l2_ctrl_s_ctrl(ov01a1s->link_freq, mode->link_freq_index); ++ __v4l2_ctrl_s_ctrl_int64(ov01a1s->pixel_rate, OV01A1S_SCLK); ++ ++ /* Update limits and set FPS to default */ ++ vblank_def = mode->vts_def - mode->height; ++ __v4l2_ctrl_modify_range(ov01a1s->vblank, ++ mode->vts_min - mode->height, ++ OV01A1S_VTS_MAX - mode->height, 1, ++ vblank_def); ++ __v4l2_ctrl_s_ctrl(ov01a1s->vblank, vblank_def); ++ h_blank = mode->hts - mode->width; ++ __v4l2_ctrl_modify_range(ov01a1s->hblank, h_blank, h_blank, 1, ++ h_blank); ++ } ++ mutex_unlock(&ov01a1s->mutex); ++ ++ return 0; ++} ++ ++static int ov01a1s_get_format(struct v4l2_subdev *sd, ++#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 14, 0) ++ struct v4l2_subdev_pad_config *cfg, ++#else ++ struct v4l2_subdev_state *sd_state, ++#endif ++ struct v4l2_subdev_format *fmt) ++{ ++ struct ov01a1s *ov01a1s = to_ov01a1s(sd); ++ ++ mutex_lock(&ov01a1s->mutex); ++ if (fmt->which == V4L2_SUBDEV_FORMAT_TRY) ++#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 14, 0) ++ fmt->format = *v4l2_subdev_get_try_format(&ov01a1s->sd, cfg, ++ fmt->pad); ++#else ++ fmt->format = *v4l2_subdev_get_try_format(&ov01a1s->sd, ++ sd_state, fmt->pad); ++#endif ++ else ++ ov01a1s_update_pad_format(ov01a1s->cur_mode, &fmt->format); ++ ++ mutex_unlock(&ov01a1s->mutex); ++ ++ return 0; ++} ++ ++static int ov01a1s_enum_mbus_code(struct v4l2_subdev *sd, ++#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 14, 0) ++ struct v4l2_subdev_pad_config *cfg, ++#else ++ struct v4l2_subdev_state *sd_state, ++#endif ++ struct v4l2_subdev_mbus_code_enum *code) ++{ ++ if (code->index > 0) ++ return -EINVAL; ++ ++ code->code = MEDIA_BUS_FMT_SGRBG10_1X10; ++ ++ return 0; ++} ++ ++static int ov01a1s_enum_frame_size(struct v4l2_subdev *sd, ++#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 14, 0) ++ struct v4l2_subdev_pad_config *cfg, ++#else ++ struct v4l2_subdev_state *sd_state, ++#endif ++ struct v4l2_subdev_frame_size_enum *fse) ++{ ++ if (fse->index >= ARRAY_SIZE(supported_modes)) ++ return -EINVAL; ++ ++ if (fse->code != MEDIA_BUS_FMT_SGRBG10_1X10) ++ return -EINVAL; ++ ++ fse->min_width = supported_modes[fse->index].width; ++ fse->max_width = fse->min_width; ++ fse->min_height = supported_modes[fse->index].height; ++ fse->max_height = fse->min_height; ++ ++ return 0; ++} ++ ++static int ov01a1s_open(struct v4l2_subdev *sd, struct v4l2_subdev_fh *fh) ++{ ++ struct ov01a1s *ov01a1s = to_ov01a1s(sd); ++ ++ mutex_lock(&ov01a1s->mutex); ++#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 14, 0) ++ ov01a1s_update_pad_format(&supported_modes[0], ++ v4l2_subdev_get_try_format(sd, fh->pad, 0)); ++#else ++ ov01a1s_update_pad_format(&supported_modes[0], ++ v4l2_subdev_get_try_format(sd, fh->state, 0)); ++#endif ++ mutex_unlock(&ov01a1s->mutex); ++ ++ return 0; ++} ++ ++static const struct v4l2_subdev_video_ops ov01a1s_video_ops = { ++ .s_stream = ov01a1s_set_stream, ++}; ++ ++static const struct v4l2_subdev_pad_ops ov01a1s_pad_ops = { ++ .set_fmt = ov01a1s_set_format, ++ .get_fmt = ov01a1s_get_format, ++ .enum_mbus_code = ov01a1s_enum_mbus_code, ++ .enum_frame_size = ov01a1s_enum_frame_size, ++}; ++ ++static const struct v4l2_subdev_ops ov01a1s_subdev_ops = { ++ .video = &ov01a1s_video_ops, ++ .pad = &ov01a1s_pad_ops, ++}; ++ ++static const struct media_entity_operations ov01a1s_subdev_entity_ops = { ++ .link_validate = v4l2_subdev_link_validate, ++}; ++ ++static const struct v4l2_subdev_internal_ops ov01a1s_internal_ops = { ++ .open = ov01a1s_open, ++}; ++ ++static int ov01a1s_identify_module(struct ov01a1s *ov01a1s) ++{ ++ struct i2c_client *client = ov01a1s->client; ++ int ret; ++ u32 val; ++ ++ ret = ov01a1s_read_reg(ov01a1s, OV01A1S_REG_CHIP_ID, 3, &val); ++ if (ret) ++ return ret; ++ ++ if (val != OV01A1S_CHIP_ID) { ++ dev_err(&client->dev, "chip id mismatch: %x!=%x", ++ OV01A1S_CHIP_ID, val); ++ return -ENXIO; ++ } ++ ++ return 0; ++} ++ ++#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 1, 0) ++static int ov01a1s_remove(struct i2c_client *client) ++#else ++static void ov01a1s_remove(struct i2c_client *client) ++#endif ++{ ++ struct v4l2_subdev *sd = i2c_get_clientdata(client); ++ struct ov01a1s *ov01a1s = to_ov01a1s(sd); ++ ++ v4l2_async_unregister_subdev(sd); ++ media_entity_cleanup(&sd->entity); ++ v4l2_ctrl_handler_free(sd->ctrl_handler); ++ pm_runtime_disable(&client->dev); ++ mutex_destroy(&ov01a1s->mutex); ++ ++#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 1, 0) ++ return 0; ++#endif ++} ++ ++#if IS_ENABLED(CONFIG_INTEL_SKL_INT3472) ++static int ov01a1s_parse_gpio(struct ov01a1s *ov01a1s) ++{ ++ struct device *dev = &ov01a1s->client->dev; ++ ++ ov01a1s->reset_gpio = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW); ++ if (IS_ERR(ov01a1s->reset_gpio)) { ++ dev_warn(dev, "error while getting reset gpio: %ld\n", ++ PTR_ERR(ov01a1s->reset_gpio)); ++ ov01a1s->reset_gpio = NULL; ++ return -EPROBE_DEFER; ++ } ++ ++ /* For optional, don't return or print warn if can't get it */ ++ ov01a1s->powerdown_gpio = ++ devm_gpiod_get_optional(dev, "powerdown", GPIOD_OUT_LOW); ++ if (IS_ERR(ov01a1s->powerdown_gpio)) { ++ dev_dbg(dev, "no powerdown gpio: %ld\n", ++ PTR_ERR(ov01a1s->powerdown_gpio)); ++ ov01a1s->powerdown_gpio = NULL; ++ } ++ ++ ov01a1s->avdd = devm_regulator_get_optional(dev, "avdd"); ++ if (IS_ERR(ov01a1s->avdd)) { ++ dev_dbg(dev, "no regulator avdd: %ld\n", ++ PTR_ERR(ov01a1s->avdd)); ++ ov01a1s->avdd = NULL; ++ } ++ ++ ov01a1s->clk = devm_clk_get_optional(dev, "clk"); ++ if (IS_ERR(ov01a1s->clk)) { ++ dev_dbg(dev, "no clk: %ld\n", PTR_ERR(ov01a1s->clk)); ++ ov01a1s->clk = NULL; ++ } ++ ++ return 0; ++} ++#endif ++ ++static int ov01a1s_parse_power(struct ov01a1s *ov01a1s) ++{ ++ int ret = 0; ++ ++#if IS_ENABLED(CONFIG_INTEL_VSC) ++ ov01a1s->conf.lane_num = OV01A1S_DATA_LANES; ++ /* frequency unit 100k */ ++ ov01a1s->conf.freq = OV01A1S_LINK_FREQ_400MHZ / 100000; ++ ret = vsc_acquire_camera_sensor(&ov01a1s->conf, NULL, NULL, &ov01a1s->status); ++ if (!ret) { ++ ov01a1s->power_type = OV01A1S_USE_INTEL_VSC; ++ return 0; ++ } else if (ret != -EAGAIN) { ++ return ret; ++ } ++#endif ++#if IS_ENABLED(CONFIG_INTEL_SKL_INT3472) ++ ret = ov01a1s_parse_gpio(ov01a1s); ++#elif IS_ENABLED(CONFIG_POWER_CTRL_LOGIC) ++ ret = power_ctrl_logic_set_power(1); ++#endif ++#if IS_ENABLED(CONFIG_INTEL_SKL_INT3472) || IS_ENABLED(CONFIG_POWER_CTRL_LOGIC) ++ if (!ret) { ++ ov01a1s->power_type = OV01A1S_USE_INT3472; ++ return 0; ++ } ++#endif ++ if (ret == -EAGAIN) ++ return -EPROBE_DEFER; ++ ++ return ret; ++} ++ ++static int ov01a1s_probe(struct i2c_client *client) ++{ ++ struct ov01a1s *ov01a1s; ++ int ret = 0; ++ ++ ov01a1s = devm_kzalloc(&client->dev, sizeof(*ov01a1s), GFP_KERNEL); ++ if (!ov01a1s) ++ return -ENOMEM; ++ ++ ov01a1s->client = client; ++ ret = ov01a1s_parse_power(ov01a1s); ++ if (ret) ++ return ret; ++ ++ v4l2_i2c_subdev_init(&ov01a1s->sd, client, &ov01a1s_subdev_ops); ++#if IS_ENABLED(CONFIG_INTEL_SKL_INT3472) ++ /* In other cases, power is up in ov01a1s_parse_power */ ++ if (ov01a1s->power_type == OV01A1S_USE_INT3472) ++ ov01a1s_power_on(&client->dev); ++#endif ++ ret = ov01a1s_identify_module(ov01a1s); ++ if (ret) { ++ dev_err(&client->dev, "failed to find sensor: %d", ret); ++ goto probe_error_power_off; ++ } ++ ++ mutex_init(&ov01a1s->mutex); ++ ov01a1s->cur_mode = &supported_modes[0]; ++ ret = ov01a1s_init_controls(ov01a1s); ++ if (ret) { ++ dev_err(&client->dev, "failed to init controls: %d", ret); ++ goto probe_error_v4l2_ctrl_handler_free; ++ } ++ ++ ov01a1s->sd.internal_ops = &ov01a1s_internal_ops; ++ ov01a1s->sd.flags |= V4L2_SUBDEV_FL_HAS_DEVNODE; ++ ov01a1s->sd.entity.ops = &ov01a1s_subdev_entity_ops; ++ ov01a1s->sd.entity.function = MEDIA_ENT_F_CAM_SENSOR; ++ ov01a1s->pad.flags = MEDIA_PAD_FL_SOURCE; ++ ret = media_entity_pads_init(&ov01a1s->sd.entity, 1, &ov01a1s->pad); ++ if (ret) { ++ dev_err(&client->dev, "failed to init entity pads: %d", ret); ++ goto probe_error_v4l2_ctrl_handler_free; ++ } ++ ++#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 13, 0) ++ ret = v4l2_async_register_subdev_sensor_common(&ov01a1s->sd); ++#else ++ ret = v4l2_async_register_subdev_sensor(&ov01a1s->sd); ++#endif ++ if (ret < 0) { ++ dev_err(&client->dev, "failed to register V4L2 subdev: %d", ++ ret); ++ goto probe_error_media_entity_cleanup; ++ } ++ ++ /* ++ * Device is already turned on by i2c-core with ACPI domain PM. ++ * Enable runtime PM and turn off the device. ++ */ ++ pm_runtime_set_active(&client->dev); ++ pm_runtime_enable(&client->dev); ++ pm_runtime_idle(&client->dev); ++ ++ return 0; ++ ++probe_error_media_entity_cleanup: ++ media_entity_cleanup(&ov01a1s->sd.entity); ++ ++probe_error_v4l2_ctrl_handler_free: ++ v4l2_ctrl_handler_free(ov01a1s->sd.ctrl_handler); ++ mutex_destroy(&ov01a1s->mutex); ++ ++probe_error_power_off: ++ ov01a1s_power_off(&client->dev); ++ ++ return ret; ++} ++ ++static const struct dev_pm_ops ov01a1s_pm_ops = { ++ SET_SYSTEM_SLEEP_PM_OPS(ov01a1s_suspend, ov01a1s_resume) ++ SET_RUNTIME_PM_OPS(ov01a1s_power_off, ov01a1s_power_on, NULL) ++}; ++ ++#ifdef CONFIG_ACPI ++static const struct acpi_device_id ov01a1s_acpi_ids[] = { ++ { "OVTI01AS" }, ++ {} ++}; ++ ++MODULE_DEVICE_TABLE(acpi, ov01a1s_acpi_ids); ++#endif ++ ++static struct i2c_driver ov01a1s_i2c_driver = { ++ .driver = { ++ .name = "ov01a1s", ++ .pm = &ov01a1s_pm_ops, ++ .acpi_match_table = ACPI_PTR(ov01a1s_acpi_ids), ++ }, ++#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 6, 0) ++ .probe_new = ov01a1s_probe, ++#else ++ .probe = ov01a1s_probe, ++#endif ++ .remove = ov01a1s_remove, ++}; ++ ++module_i2c_driver(ov01a1s_i2c_driver); ++ ++MODULE_AUTHOR("Xu, Chongyang <chongyang.xu@intel.com>"); ++MODULE_AUTHOR("Lai, Jim <jim.lai@intel.com>"); ++MODULE_AUTHOR("Qiu, Tianshu <tian.shu.qiu@intel.com>"); ++MODULE_AUTHOR("Shawn Tu <shawnx.tu@intel.com>"); ++MODULE_AUTHOR("Bingbu Cao <bingbu.cao@intel.com>"); ++MODULE_DESCRIPTION("OmniVision OV01A1S sensor driver"); ++MODULE_LICENSE("GPL v2"); +-- +2.43.0 + +From 07d707663a69f65c366d3ac75c2bf41749f33224 Mon Sep 17 00:00:00 2001 +From: Hans de Goede <hdegoede@redhat.com> +Date: Mon, 6 Nov 2023 12:33:56 +0100 +Subject: [PATCH 18/31] media: ov01a1s: Remove non upstream iVSC support + +Signed-off-by: Hans de Goede <hdegoede@redhat.com> +--- + drivers/media/i2c/ov01a1s.c | 71 ------------------------------------- + 1 file changed, 71 deletions(-) + +diff --git a/drivers/media/i2c/ov01a1s.c b/drivers/media/i2c/ov01a1s.c +index 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 <linux/vsc.h> +-#endif + + #define OV01A1S_LINK_FREQ_400MHZ 400000000ULL + #define OV01A1S_SCLK 40000000LL +@@ -302,13 +299,6 @@ struct ov01a1s { + struct v4l2_ctrl *vblank; + struct v4l2_ctrl *hblank; + struct v4l2_ctrl *exposure; +-#if IS_ENABLED(CONFIG_INTEL_VSC) +- struct v4l2_ctrl *privacy_status; +- +- /* VSC settings */ +- struct vsc_mipi_config conf; +- struct vsc_camera_status status; +-#endif + + /* Current mode */ + const struct ov01a1s_mode *cur_mode; +@@ -334,9 +324,6 @@ struct ov01a1s { + OV01A1S_USE_DEFAULT = 0, + #if IS_ENABLED(CONFIG_INTEL_SKL_INT3472) || IS_ENABLED(CONFIG_POWER_CTRL_LOGIC) + OV01A1S_USE_INT3472 = 1, +-#endif +-#if IS_ENABLED(CONFIG_INTEL_VSC) +- OV01A1S_USE_INTEL_VSC = 2, + #endif + } power_type; + +@@ -505,12 +492,6 @@ static int ov01a1s_set_ctrl(struct v4l2_ctrl *ctrl) + ret = ov01a1s_test_pattern(ov01a1s, ctrl->val); + break; + +-#if IS_ENABLED(CONFIG_INTEL_VSC) +- case V4L2_CID_PRIVACY: +- dev_dbg(&client->dev, "set privacy to %d", ctrl->val); +- break; +- +-#endif + default: + ret = -EINVAL; + break; +@@ -535,11 +516,7 @@ static int ov01a1s_init_controls(struct ov01a1s *ov01a1s) + int ret = 0; + + ctrl_hdlr = &ov01a1s->ctrl_handler; +-#if IS_ENABLED(CONFIG_INTEL_VSC) +- ret = v4l2_ctrl_handler_init(ctrl_hdlr, 9); +-#else + ret = v4l2_ctrl_handler_init(ctrl_hdlr, 8); +-#endif + if (ret) + return ret; + +@@ -572,12 +549,6 @@ static int ov01a1s_init_controls(struct ov01a1s *ov01a1s) + 1, h_blank); + if (ov01a1s->hblank) + ov01a1s->hblank->flags |= V4L2_CTRL_FLAG_READ_ONLY; +-#if IS_ENABLED(CONFIG_INTEL_VSC) +- ov01a1s->privacy_status = v4l2_ctrl_new_std(ctrl_hdlr, +- &ov01a1s_ctrl_ops, +- V4L2_CID_PRIVACY, +- 0, 1, 1, 0); +-#endif + + v4l2_ctrl_new_std(ctrl_hdlr, &ov01a1s_ctrl_ops, V4L2_CID_ANALOGUE_GAIN, + OV01A1S_ANAL_GAIN_MIN, OV01A1S_ANAL_GAIN_MAX, +@@ -613,16 +584,6 @@ static void ov01a1s_update_pad_format(const struct ov01a1s_mode *mode, + fmt->field = V4L2_FIELD_NONE; + } + +-#if IS_ENABLED(CONFIG_INTEL_VSC) +-static void ov01a1s_vsc_privacy_callback(void *handle, +- enum vsc_privacy_status status) +-{ +- struct ov01a1s *ov01a1s = handle; +- +- v4l2_ctrl_s_ctrl(ov01a1s->privacy_status, !status); +-} +- +-#endif + static int ov01a1s_start_streaming(struct ov01a1s *ov01a1s) + { + struct i2c_client *client = ov01a1s->client; +@@ -722,13 +683,6 @@ static int ov01a1s_power_off(struct device *dev) + if (ov01a1s->power_type == OV01A1S_USE_INT3472) + ret = power_ctrl_logic_set_power(0); + #endif +-#if IS_ENABLED(CONFIG_INTEL_VSC) +- if (ov01a1s->power_type == OV01A1S_USE_INTEL_VSC) { +- ret = vsc_release_camera_sensor(&ov01a1s->status); +- if (ret && ret != -EAGAIN) +- dev_err(dev, "Release VSC failed"); +- } +-#endif + + return ret; + } +@@ -756,19 +710,6 @@ static int ov01a1s_power_on(struct device *dev) + if (ov01a1s->power_type == OV01A1S_USE_INT3472) + ret = power_ctrl_logic_set_power(1); + #endif +-#if IS_ENABLED(CONFIG_INTEL_VSC) +- if (ov01a1s->power_type == OV01A1S_USE_INTEL_VSC) { +- ret = vsc_acquire_camera_sensor(&ov01a1s->conf, +- ov01a1s_vsc_privacy_callback, +- ov01a1s, &ov01a1s->status); +- if (ret && ret != -EAGAIN) { +- dev_err(dev, "Acquire VSC failed"); +- return ret; +- } +- __v4l2_ctrl_s_ctrl(ov01a1s->privacy_status, +- !(ov01a1s->status.status)); +- } +-#endif + + return ret; + } +@@ -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 <hdegoede@redhat.com> +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 <bingbu.cao@intel.com> +Signed-off-by: Hans de Goede <hdegoede@redhat.com> +Signed-off-by: Sakari Ailus <sakari.ailus@linux.intel.com> +Signed-off-by: Hans Verkuil <hverkuil-cisco@xs4all.nl> +--- + 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 <asm/unaligned.h> + #include <linux/acpi.h> + #include <linux/delay.h> ++#include <linux/gpio/consumer.h> + #include <linux/i2c.h> + #include <linux/module.h> + #include <linux/pm_runtime.h> +@@ -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 <hdegoede@redhat.com> +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 <bingbu.cao@intel.com> +Signed-off-by: Hans de Goede <hdegoede@redhat.com> +Signed-off-by: Sakari Ailus <sakari.ailus@linux.intel.com> +Signed-off-by: Hans Verkuil <hverkuil-cisco@xs4all.nl> +--- + 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 <asm/unaligned.h> + #include <linux/acpi.h> ++#include <linux/clk.h> + #include <linux/delay.h> + #include <linux/gpio/consumer.h> + #include <linux/i2c.h> +@@ -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 <hdegoede@redhat.com> +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 <hdegoede@redhat.com> +Signed-off-by: Sakari Ailus <sakari.ailus@linux.intel.com> +Signed-off-by: Hans Verkuil <hverkuil-cisco@xs4all.nl> +--- + 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 <hdegoede@redhat.com> +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 <hdegoede@redhat.com> +Signed-off-by: Sakari Ailus <sakari.ailus@linux.intel.com> +Signed-off-by: Hans Verkuil <hverkuil-cisco@xs4all.nl> +--- + 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 <hdegoede@redhat.com> +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 <hdegoede@redhat.com> +Signed-off-by: Sakari Ailus <sakari.ailus@linux.intel.com> +Signed-off-by: Hans Verkuil <hverkuil-cisco@xs4all.nl> +--- + 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 <hdegoede@redhat.com> +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 <hdegoede@redhat.com> +Signed-off-by: Sakari Ailus <sakari.ailus@linux.intel.com> +Signed-off-by: Hans Verkuil <hverkuil-cisco@xs4all.nl> +--- + 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 <media/v4l2-fwnode.h> + + #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 <hdegoede@redhat.com> +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 <hdegoede@redhat.com> +Signed-off-by: Sakari Ailus <sakari.ailus@linux.intel.com> +Signed-off-by: Hans Verkuil <hverkuil-cisco@xs4all.nl> +--- + 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 <hdegoede@redhat.com> +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 <hdegoede@redhat.com> +Signed-off-by: Sakari Ailus <sakari.ailus@linux.intel.com> +Signed-off-by: Hans Verkuil <hverkuil-cisco@xs4all.nl> +--- + 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 <hdegoede@redhat.com> +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 <hdegoede@redhat.com> +--- + drivers/media/i2c/hi556.c | 13 +++++++------ + 1 file changed, 7 insertions(+), 6 deletions(-) + +diff --git a/drivers/media/i2c/hi556.c b/drivers/media/i2c/hi556.c +index 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 <hdegoede@redhat.com> +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 <hdegoede@redhat.com> +--- + drivers/media/i2c/hi556.c | 45 ++++++++++++++++++++++++++++++++++++++- + 1 file changed, 44 insertions(+), 1 deletion(-) + +diff --git a/drivers/media/i2c/hi556.c b/drivers/media/i2c/hi556.c +index 258614b33f51..7398391989ea 100644 +--- a/drivers/media/i2c/hi556.c ++++ b/drivers/media/i2c/hi556.c +@@ -4,6 +4,7 @@ + #include <asm/unaligned.h> + #include <linux/acpi.h> + #include <linux/delay.h> ++#include <linux/gpio/consumer.h> + #include <linux/i2c.h> + #include <linux/module.h> + #include <linux/pm_runtime.h> +@@ -633,6 +634,9 @@ struct hi556 { + struct v4l2_ctrl *hblank; + struct v4l2_ctrl *exposure; + ++ /* GPIOs, clocks, etc. */ ++ struct gpio_desc *reset_gpio; ++ + /* Current mode */ + const struct hi556_mode *cur_mode; + +@@ -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 <hdegoede@redhat.com> +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 <hdegoede@redhat.com> +--- + drivers/media/i2c/hi556.c | 13 +++++++++++++ + 1 file changed, 13 insertions(+) + +diff --git a/drivers/media/i2c/hi556.c b/drivers/media/i2c/hi556.c +index 7398391989ea..fb6ba6984e38 100644 +--- a/drivers/media/i2c/hi556.c ++++ b/drivers/media/i2c/hi556.c +@@ -3,6 +3,7 @@ + + #include <asm/unaligned.h> + #include <linux/acpi.h> ++#include <linux/clk.h> + #include <linux/delay.h> + #include <linux/gpio/consumer.h> + #include <linux/i2c.h> +@@ -636,6 +637,7 @@ struct hi556 { + + /* GPIOs, clocks, etc. */ + struct gpio_desc *reset_gpio; ++ struct clk *clk; + + /* Current mode */ + const struct hi556_mode *cur_mode; +@@ -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 <hdegoede@redhat.com> +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 <hdegoede@redhat.com> +--- + drivers/media/i2c/hi556.c | 24 ++++++++++++++++++++++++ + 1 file changed, 24 insertions(+) + +diff --git a/drivers/media/i2c/hi556.c b/drivers/media/i2c/hi556.c +index fb6ba6984e38..90eff282a6e2 100644 +--- a/drivers/media/i2c/hi556.c ++++ b/drivers/media/i2c/hi556.c +@@ -9,6 +9,7 @@ + #include <linux/i2c.h> + #include <linux/module.h> + #include <linux/pm_runtime.h> ++#include <linux/regulator/consumer.h> + #include <media/v4l2-ctrls.h> + #include <media/v4l2-device.h> + #include <media/v4l2-fwnode.h> +@@ -638,6 +639,7 @@ struct hi556 { + /* GPIOs, clocks, etc. */ + struct gpio_desc *reset_gpio; + struct clk *clk; ++ struct regulator *avdd; + + /* Current mode */ + const struct hi556_mode *cur_mode; +@@ -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 + |