about summary refs log tree commit diff
path: root/users/flokli/ipu6-softisp/kernel
diff options
context:
space:
mode:
authorFlorian Klink <flokli@flokli.de>2024-02-13T10·49+0200
committerclbot <clbot@tvl.fyi>2024-02-13T15·37+0000
commit41cd3c44d2f76a0df0e279609c17ef62e7e622f4 (patch)
tree21f631381e86af0ebe9b8ca2703f4a4e2fe0ff7a /users/flokli/ipu6-softisp/kernel
parent28173ca4b92d5bb723fe77ae217bcf4f3bb6ce0d (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/ipu6-softisp/kernel')
-rw-r--r--users/flokli/ipu6-softisp/kernel/softisp.patch17143
1 files changed, 17143 insertions, 0 deletions
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, &ltrdid);
++
++		if (calc_fill_time_us <= ltrdid.lut_fill_time.bits.th0)
++			ltr = 0;
++		else if (calc_fill_time_us <= ltrdid.lut_fill_time.bits.th1)
++			ltr = ltrdid.lut_ltr.bits.val0;
++		else if (calc_fill_time_us <= ltrdid.lut_fill_time.bits.th2)
++			ltr = ltrdid.lut_ltr.bits.val1;
++		else if (calc_fill_time_us <= ltrdid.lut_fill_time.bits.th3)
++			ltr = ltrdid.lut_ltr.bits.val2;
++		else
++			ltr = ltrdid.lut_ltr.bits.val3;
++
++		did = calc_fill_time_us - ltr;
++		ltr_did_type = LTR_IWAKE_ON;
++	}
++
++	set_iwake_ltrdid(isys, ltr, did, ltr_did_type);
++
++	/* calculate iwake threshold with 2KB granularity pages */
++	threshold_bytes = did * isys_pb_datarate_mbs;
++	iwake_threshold = max_t(u32, 1, threshold_bytes >> shift);
++	iwake_threshold = min_t(u32, iwake_threshold, max_sram_size);
++
++	mutex_lock(&iwake_watermark->mutex);
++	if (isys->pdata->ipdata->enhanced_iwake) {
++		set_iwake_register(isys, GDA_IWAKE_THRESHOLD_INDEX,
++				   DEFAULT_IWAKE_THRESHOLD);
++		/* calculate number of pages that will be filled in 10 usec */
++		page_num = (DEFAULT_MEM_OPEN_TIME * isys_pb_datarate_mbs) /
++			ISF_DMA_TOP_GDA_PROFERTY_PAGE_SIZE;
++		page_num += ((DEFAULT_MEM_OPEN_TIME * isys_pb_datarate_mbs) %
++			     ISF_DMA_TOP_GDA_PROFERTY_PAGE_SIZE) ? 1 : 0;
++		mem_open_threshold = isys->pdata->ipdata->memopen_threshold;
++		mem_open_threshold = max_t(u32, mem_open_threshold, page_num);
++		dev_dbg(dev, "mem_open_threshold: %u\n", mem_open_threshold);
++		set_iwake_register(isys, GDA_MEMOPEN_THRESHOLD_INDEX,
++				   mem_open_threshold);
++	} else {
++		set_iwake_register(isys, GDA_IWAKE_THRESHOLD_INDEX,
++				   iwake_threshold);
++	}
++
++	iwake_critical_threshold = iwake_threshold +
++		(IS_PIXEL_BUFFER_PAGES - iwake_threshold) / 2;
++
++	dev_dbg(dev, "threshold: %u critical: %u\n", iwake_threshold,
++		iwake_critical_threshold);
++
++	set_iwake_register(isys, GDA_IRQ_CRITICAL_THRESHOLD_INDEX,
++			   iwake_critical_threshold);
++
++	writel(VAL_PKGC_PMON_CFG_RESET,
++	       isys->adev->isp->base + REG_PKGC_PMON_CFG);
++	writel(VAL_PKGC_PMON_CFG_START,
++	       isys->adev->isp->base + REG_PKGC_PMON_CFG);
++unlock_exit:
++	mutex_unlock(&iwake_watermark->mutex);
++}
++
++static void isys_iwake_watermark_init(struct ipu6_isys *isys)
++{
++	struct isys_iwake_watermark *iwake_watermark = &isys->iwake_watermark;
++
++	INIT_LIST_HEAD(&iwake_watermark->video_list);
++	mutex_init(&iwake_watermark->mutex);
++
++	iwake_watermark->ltrdid.lut_ltr.value = 0;
++	iwake_watermark->isys = isys;
++	iwake_watermark->iwake_enabled = false;
++	iwake_watermark->force_iwake_disable = false;
++}
++
++static void isys_iwake_watermark_cleanup(struct ipu6_isys *isys)
++{
++	struct isys_iwake_watermark *iwake_watermark = &isys->iwake_watermark;
++
++	mutex_lock(&iwake_watermark->mutex);
++	list_del(&iwake_watermark->video_list);
++	mutex_unlock(&iwake_watermark->mutex);
++
++	mutex_destroy(&iwake_watermark->mutex);
++}
++
++/* The .bound() notifier callback when a match is found */
++static int isys_notifier_bound(struct v4l2_async_notifier *notifier,
++			       struct v4l2_subdev *sd,
++			       struct v4l2_async_connection *asc)
++{
++	struct ipu6_isys *isys =
++		container_of(notifier, struct ipu6_isys, notifier);
++	struct sensor_async_sd *s_asd =
++		container_of(asc, struct sensor_async_sd, asc);
++	int ret;
++
++	ret = ipu_bridge_instantiate_vcm(sd->dev);
++	if (ret) {
++		dev_err(&isys->adev->auxdev.dev, "instantiate vcm failed\n");
++		return ret;
++	}
++
++	dev_dbg(&isys->adev->auxdev.dev, "bind %s nlanes is %d port is %d\n",
++		sd->name, s_asd->csi2.nlanes, s_asd->csi2.port);
++	ret = isys_complete_ext_device_registration(isys, sd, &s_asd->csi2);
++	if (ret)
++		return ret;
++
++	return v4l2_device_register_subdev_nodes(&isys->v4l2_dev);
++}
++
++static int isys_notifier_complete(struct v4l2_async_notifier *notifier)
++{
++	struct ipu6_isys *isys =
++		container_of(notifier, struct ipu6_isys, notifier);
++
++	return v4l2_device_register_subdev_nodes(&isys->v4l2_dev);
++}
++
++static const struct v4l2_async_notifier_operations isys_async_ops = {
++	.bound = isys_notifier_bound,
++	.complete = isys_notifier_complete,
++};
++
++#define ISYS_MAX_PORTS 8
++static int isys_notifier_init(struct ipu6_isys *isys)
++{
++	struct ipu6_device *isp = isys->adev->isp;
++	struct device *dev = &isp->pdev->dev;
++	unsigned int i;
++	int ret;
++
++	v4l2_async_nf_init(&isys->notifier, &isys->v4l2_dev);
++
++	for (i = 0; i < ISYS_MAX_PORTS; i++) {
++		struct v4l2_fwnode_endpoint vep = {
++			.bus_type = V4L2_MBUS_CSI2_DPHY
++		};
++		struct sensor_async_sd *s_asd;
++		struct fwnode_handle *ep;
++
++		ep = fwnode_graph_get_endpoint_by_id(dev_fwnode(dev), i, 0,
++						FWNODE_GRAPH_ENDPOINT_NEXT);
++		if (!ep)
++			continue;
++
++		ret = v4l2_fwnode_endpoint_parse(ep, &vep);
++		if (ret) {
++			dev_err(dev, "fwnode endpoint parse failed: %d\n", ret);
++			goto err_parse;
++		}
++
++		s_asd = v4l2_async_nf_add_fwnode_remote(&isys->notifier, ep,
++							struct sensor_async_sd);
++		if (IS_ERR(s_asd)) {
++			ret = PTR_ERR(s_asd);
++			dev_err(dev, "add remove fwnode failed: %d\n", ret);
++			goto err_parse;
++		}
++
++		s_asd->csi2.port = vep.base.port;
++		s_asd->csi2.nlanes = vep.bus.mipi_csi2.num_data_lanes;
++
++		dev_dbg(dev, "remote endpoint port %d with %d lanes added\n",
++			s_asd->csi2.port, s_asd->csi2.nlanes);
++
++		fwnode_handle_put(ep);
++
++		continue;
++
++err_parse:
++		fwnode_handle_put(ep);
++		return ret;
++	}
++
++	isys->notifier.ops = &isys_async_ops;
++	ret = v4l2_async_nf_register(&isys->notifier);
++	if (ret) {
++		dev_err(dev, "failed to register async notifier : %d\n", ret);
++		v4l2_async_nf_cleanup(&isys->notifier);
++	}
++
++	return ret;
++}
++
++static void isys_notifier_cleanup(struct ipu6_isys *isys)
++{
++	v4l2_async_nf_unregister(&isys->notifier);
++	v4l2_async_nf_cleanup(&isys->notifier);
++}
++
++static int isys_register_devices(struct ipu6_isys *isys)
++{
++	struct device *dev = &isys->adev->auxdev.dev;
++	struct pci_dev *pdev = isys->adev->isp->pdev;
++	int ret;
++
++	isys->media_dev.dev = dev;
++	media_device_pci_init(&isys->media_dev,
++			      pdev, IPU6_MEDIA_DEV_MODEL_NAME);
++
++	strscpy(isys->v4l2_dev.name, isys->media_dev.model,
++		sizeof(isys->v4l2_dev.name));
++
++	ret = media_device_register(&isys->media_dev);
++	if (ret < 0)
++		goto out_media_device_unregister;
++
++	isys->v4l2_dev.mdev = &isys->media_dev;
++	isys->v4l2_dev.ctrl_handler = NULL;
++
++	ret = v4l2_device_register(dev->parent, &isys->v4l2_dev);
++	if (ret < 0)
++		goto out_media_device_unregister;
++
++	ret = isys_register_video_devices(isys);
++	if (ret)
++		goto out_v4l2_device_unregister;
++
++	ret = isys_csi2_register_subdevices(isys);
++	if (ret)
++		goto out_isys_unregister_video_device;
++
++	ret = isys_csi2_create_media_links(isys);
++	if (ret)
++		goto out_isys_unregister_subdevices;
++
++	ret = isys_notifier_init(isys);
++	if (ret)
++		goto out_isys_unregister_subdevices;
++
++	return 0;
++
++out_isys_unregister_subdevices:
++	isys_csi2_unregister_subdevices(isys);
++
++out_isys_unregister_video_device:
++	isys_unregister_video_devices(isys);
++
++out_v4l2_device_unregister:
++	v4l2_device_unregister(&isys->v4l2_dev);
++
++out_media_device_unregister:
++	media_device_unregister(&isys->media_dev);
++	media_device_cleanup(&isys->media_dev);
++
++	dev_err(dev, "failed to register isys devices\n");
++
++	return ret;
++}
++
++static void isys_unregister_devices(struct ipu6_isys *isys)
++{
++	isys_unregister_video_devices(isys);
++	isys_csi2_unregister_subdevices(isys);
++	v4l2_device_unregister(&isys->v4l2_dev);
++	media_device_unregister(&isys->media_dev);
++	media_device_cleanup(&isys->media_dev);
++}
++
++static int isys_runtime_pm_resume(struct device *dev)
++{
++	struct ipu6_bus_device *adev = to_ipu6_bus_device(dev);
++	struct ipu6_isys *isys = ipu6_bus_get_drvdata(adev);
++	struct ipu6_device *isp = adev->isp;
++	unsigned long flags;
++	int ret;
++
++	if (!isys)
++		return 0;
++
++	ret = ipu6_mmu_hw_init(adev->mmu);
++	if (ret)
++		return ret;
++
++	cpu_latency_qos_update_request(&isys->pm_qos, ISYS_PM_QOS_VALUE);
++
++	ret = ipu6_buttress_start_tsc_sync(isp);
++	if (ret)
++		return ret;
++
++	spin_lock_irqsave(&isys->power_lock, flags);
++	isys->power = 1;
++	spin_unlock_irqrestore(&isys->power_lock, flags);
++
++	isys_setup_hw(isys);
++
++	set_iwake_ltrdid(isys, 0, 0, LTR_ISYS_ON);
++
++	return 0;
++}
++
++static int isys_runtime_pm_suspend(struct device *dev)
++{
++	struct ipu6_bus_device *adev = to_ipu6_bus_device(dev);
++	struct ipu6_isys *isys;
++	unsigned long flags;
++
++	isys = dev_get_drvdata(dev);
++	if (!isys)
++		return 0;
++
++	spin_lock_irqsave(&isys->power_lock, flags);
++	isys->power = 0;
++	spin_unlock_irqrestore(&isys->power_lock, flags);
++
++	mutex_lock(&isys->mutex);
++	isys->need_reset = false;
++	mutex_unlock(&isys->mutex);
++
++	isys->phy_termcal_val = 0;
++	cpu_latency_qos_update_request(&isys->pm_qos, PM_QOS_DEFAULT_VALUE);
++
++	set_iwake_ltrdid(isys, 0, 0, LTR_ISYS_OFF);
++
++	ipu6_mmu_hw_cleanup(adev->mmu);
++
++	return 0;
++}
++
++static int isys_suspend(struct device *dev)
++{
++	struct ipu6_isys *isys = dev_get_drvdata(dev);
++
++	/* If stream is open, refuse to suspend */
++	if (isys->stream_opened)
++		return -EBUSY;
++
++	return 0;
++}
++
++static int isys_resume(struct device *dev)
++{
++	return 0;
++}
++
++static const struct dev_pm_ops isys_pm_ops = {
++	.runtime_suspend = isys_runtime_pm_suspend,
++	.runtime_resume = isys_runtime_pm_resume,
++	.suspend = isys_suspend,
++	.resume = isys_resume,
++};
++
++static void isys_remove(struct auxiliary_device *auxdev)
++{
++	struct ipu6_bus_device *adev = auxdev_to_adev(auxdev);
++	struct ipu6_isys *isys = dev_get_drvdata(&auxdev->dev);
++	struct ipu6_device *isp = adev->isp;
++	struct isys_fw_msgs *fwmsg, *safe;
++	unsigned int i;
++
++	list_for_each_entry_safe(fwmsg, safe, &isys->framebuflist, head)
++		dma_free_attrs(&auxdev->dev, sizeof(struct isys_fw_msgs),
++			       fwmsg, fwmsg->dma_addr, 0);
++
++	list_for_each_entry_safe(fwmsg, safe, &isys->framebuflist_fw, head)
++		dma_free_attrs(&auxdev->dev, sizeof(struct isys_fw_msgs),
++			       fwmsg, fwmsg->dma_addr, 0);
++
++	isys_unregister_devices(isys);
++	isys_notifier_cleanup(isys);
++
++	cpu_latency_qos_remove_request(&isys->pm_qos);
++
++	if (!isp->secure_mode) {
++		ipu6_cpd_free_pkg_dir(adev);
++		ipu6_buttress_unmap_fw_image(adev, &adev->fw_sgt);
++		release_firmware(adev->fw);
++	}
++
++	for (i = 0; i < IPU6_ISYS_MAX_STREAMS; i++)
++		mutex_destroy(&isys->streams[i].mutex);
++
++	isys_iwake_watermark_cleanup(isys);
++	mutex_destroy(&isys->stream_mutex);
++	mutex_destroy(&isys->mutex);
++}
++
++static int alloc_fw_msg_bufs(struct ipu6_isys *isys, int amount)
++{
++	struct device *dev = &isys->adev->auxdev.dev;
++	struct isys_fw_msgs *addr;
++	dma_addr_t dma_addr;
++	unsigned long flags;
++	unsigned int i;
++
++	for (i = 0; i < amount; i++) {
++		addr = dma_alloc_attrs(dev, sizeof(struct isys_fw_msgs),
++				       &dma_addr, GFP_KERNEL, 0);
++		if (!addr)
++			break;
++		addr->dma_addr = dma_addr;
++
++		spin_lock_irqsave(&isys->listlock, flags);
++		list_add(&addr->head, &isys->framebuflist);
++		spin_unlock_irqrestore(&isys->listlock, flags);
++	}
++
++	if (i == amount)
++		return 0;
++
++	spin_lock_irqsave(&isys->listlock, flags);
++	while (!list_empty(&isys->framebuflist)) {
++		addr = list_first_entry(&isys->framebuflist,
++					struct isys_fw_msgs, head);
++		list_del(&addr->head);
++		spin_unlock_irqrestore(&isys->listlock, flags);
++		dma_free_attrs(dev, sizeof(struct isys_fw_msgs), addr,
++			       addr->dma_addr, 0);
++		spin_lock_irqsave(&isys->listlock, flags);
++	}
++	spin_unlock_irqrestore(&isys->listlock, flags);
++
++	return -ENOMEM;
++}
++
++struct isys_fw_msgs *ipu6_get_fw_msg_buf(struct ipu6_isys_stream *stream)
++{
++	struct ipu6_isys *isys = stream->isys;
++	struct device *dev = &isys->adev->auxdev.dev;
++	struct isys_fw_msgs *msg;
++	unsigned long flags;
++	int ret;
++
++	spin_lock_irqsave(&isys->listlock, flags);
++	if (list_empty(&isys->framebuflist)) {
++		spin_unlock_irqrestore(&isys->listlock, flags);
++		dev_dbg(dev, "Frame list empty\n");
++
++		ret = alloc_fw_msg_bufs(isys, 5);
++		if (ret < 0)
++			return NULL;
++
++		spin_lock_irqsave(&isys->listlock, flags);
++		if (list_empty(&isys->framebuflist)) {
++			spin_unlock_irqrestore(&isys->listlock, flags);
++			dev_err(dev, "Frame list empty\n");
++			return NULL;
++		}
++	}
++	msg = list_last_entry(&isys->framebuflist, struct isys_fw_msgs, head);
++	list_move(&msg->head, &isys->framebuflist_fw);
++	spin_unlock_irqrestore(&isys->listlock, flags);
++	memset(&msg->fw_msg, 0, sizeof(msg->fw_msg));
++
++	return msg;
++}
++
++void ipu6_cleanup_fw_msg_bufs(struct ipu6_isys *isys)
++{
++	struct isys_fw_msgs *fwmsg, *fwmsg0;
++	unsigned long flags;
++
++	spin_lock_irqsave(&isys->listlock, flags);
++	list_for_each_entry_safe(fwmsg, fwmsg0, &isys->framebuflist_fw, head)
++		list_move(&fwmsg->head, &isys->framebuflist);
++	spin_unlock_irqrestore(&isys->listlock, flags);
++}
++
++void ipu6_put_fw_msg_buf(struct ipu6_isys *isys, u64 data)
++{
++	struct isys_fw_msgs *msg;
++	unsigned long flags;
++	u64 *ptr = (u64 *)data;
++
++	if (!ptr)
++		return;
++
++	spin_lock_irqsave(&isys->listlock, flags);
++	msg = container_of(ptr, struct isys_fw_msgs, fw_msg.dummy);
++	list_move(&msg->head, &isys->framebuflist);
++	spin_unlock_irqrestore(&isys->listlock, flags);
++}
++
++static int isys_probe(struct auxiliary_device *auxdev,
++		      const struct auxiliary_device_id *auxdev_id)
++{
++	struct ipu6_bus_device *adev = auxdev_to_adev(auxdev);
++	struct ipu6_device *isp = adev->isp;
++	const struct firmware *fw;
++	struct ipu6_isys *isys;
++	unsigned int i;
++	int ret;
++
++	if (!isp->bus_ready_to_probe)
++		return -EPROBE_DEFER;
++
++	isys = devm_kzalloc(&auxdev->dev, sizeof(*isys), GFP_KERNEL);
++	if (!isys)
++		return -ENOMEM;
++
++	ret = ipu6_mmu_hw_init(adev->mmu);
++	if (ret)
++		return ret;
++
++	adev->auxdrv_data =
++		(const struct ipu6_auxdrv_data *)auxdev_id->driver_data;
++	adev->auxdrv = to_auxiliary_drv(auxdev->dev.driver);
++	isys->adev = adev;
++	isys->pdata = adev->pdata;
++
++	/* initial sensor type */
++	isys->sensor_type = isys->pdata->ipdata->sensor_type_start;
++
++	spin_lock_init(&isys->streams_lock);
++	spin_lock_init(&isys->power_lock);
++	isys->power = 0;
++	isys->phy_termcal_val = 0;
++
++	mutex_init(&isys->mutex);
++	mutex_init(&isys->stream_mutex);
++
++	spin_lock_init(&isys->listlock);
++	INIT_LIST_HEAD(&isys->framebuflist);
++	INIT_LIST_HEAD(&isys->framebuflist_fw);
++
++	isys->line_align = IPU6_ISYS_2600_MEM_LINE_ALIGN;
++	isys->icache_prefetch = 0;
++
++	dev_set_drvdata(&auxdev->dev, isys);
++
++	isys_stream_init(isys);
++
++	if (!isp->secure_mode) {
++		fw = isp->cpd_fw;
++		ret = ipu6_buttress_map_fw_image(adev, fw, &adev->fw_sgt);
++		if (ret)
++			goto release_firmware;
++
++		ret = ipu6_cpd_create_pkg_dir(adev, isp->cpd_fw->data);
++		if (ret)
++			goto remove_shared_buffer;
++	}
++
++	cpu_latency_qos_add_request(&isys->pm_qos, PM_QOS_DEFAULT_VALUE);
++
++	ret = alloc_fw_msg_bufs(isys, 20);
++	if (ret < 0)
++		goto out_remove_pkg_dir_shared_buffer;
++
++	isys_iwake_watermark_init(isys);
++
++	if (is_ipu6se(adev->isp->hw_ver))
++		isys->phy_set_power = ipu6_isys_jsl_phy_set_power;
++	else if (is_ipu6ep_mtl(adev->isp->hw_ver))
++		isys->phy_set_power = ipu6_isys_dwc_phy_set_power;
++	else
++		isys->phy_set_power = ipu6_isys_mcd_phy_set_power;
++
++	ret = isys_register_devices(isys);
++	if (ret)
++		goto out_remove_pkg_dir_shared_buffer;
++
++	ipu6_mmu_hw_cleanup(adev->mmu);
++
++	return 0;
++
++out_remove_pkg_dir_shared_buffer:
++	if (!isp->secure_mode)
++		ipu6_cpd_free_pkg_dir(adev);
++remove_shared_buffer:
++	if (!isp->secure_mode)
++		ipu6_buttress_unmap_fw_image(adev, &adev->fw_sgt);
++release_firmware:
++	if (!isp->secure_mode)
++		release_firmware(adev->fw);
++
++	for (i = 0; i < IPU6_ISYS_MAX_STREAMS; i++)
++		mutex_destroy(&isys->streams[i].mutex);
++
++	mutex_destroy(&isys->mutex);
++	mutex_destroy(&isys->stream_mutex);
++
++	ipu6_mmu_hw_cleanup(adev->mmu);
++
++	return ret;
++}
++
++struct fwmsg {
++	int type;
++	char *msg;
++	bool valid_ts;
++};
++
++static const struct fwmsg fw_msg[] = {
++	{IPU6_FW_ISYS_RESP_TYPE_STREAM_OPEN_DONE, "STREAM_OPEN_DONE", 0},
++	{IPU6_FW_ISYS_RESP_TYPE_STREAM_CLOSE_ACK, "STREAM_CLOSE_ACK", 0},
++	{IPU6_FW_ISYS_RESP_TYPE_STREAM_START_ACK, "STREAM_START_ACK", 0},
++	{IPU6_FW_ISYS_RESP_TYPE_STREAM_START_AND_CAPTURE_ACK,
++	 "STREAM_START_AND_CAPTURE_ACK", 0},
++	{IPU6_FW_ISYS_RESP_TYPE_STREAM_STOP_ACK, "STREAM_STOP_ACK", 0},
++	{IPU6_FW_ISYS_RESP_TYPE_STREAM_FLUSH_ACK, "STREAM_FLUSH_ACK", 0},
++	{IPU6_FW_ISYS_RESP_TYPE_PIN_DATA_READY, "PIN_DATA_READY", 1},
++	{IPU6_FW_ISYS_RESP_TYPE_STREAM_CAPTURE_ACK, "STREAM_CAPTURE_ACK", 0},
++	{IPU6_FW_ISYS_RESP_TYPE_STREAM_START_AND_CAPTURE_DONE,
++	 "STREAM_START_AND_CAPTURE_DONE", 1},
++	{IPU6_FW_ISYS_RESP_TYPE_STREAM_CAPTURE_DONE, "STREAM_CAPTURE_DONE", 1},
++	{IPU6_FW_ISYS_RESP_TYPE_FRAME_SOF, "FRAME_SOF", 1},
++	{IPU6_FW_ISYS_RESP_TYPE_FRAME_EOF, "FRAME_EOF", 1},
++	{IPU6_FW_ISYS_RESP_TYPE_STATS_DATA_READY, "STATS_READY", 1},
++	{-1, "UNKNOWN MESSAGE", 0}
++};
++
++static u32 resp_type_to_index(int type)
++{
++	unsigned int i;
++
++	for (i = 0; i < ARRAY_SIZE(fw_msg); i++)
++		if (fw_msg[i].type == type)
++			return i;
++
++	return  ARRAY_SIZE(fw_msg) - 1;
++}
++
++static int isys_isr_one(struct ipu6_bus_device *adev)
++{
++	struct ipu6_isys *isys = ipu6_bus_get_drvdata(adev);
++	struct ipu6_fw_isys_resp_info_abi *resp;
++	struct ipu6_isys_stream *stream;
++	struct ipu6_isys_csi2 *csi2 = NULL;
++	u32 index;
++	u64 ts;
++
++	if (!isys->fwcom)
++		return 1;
++
++	resp = ipu6_fw_isys_get_resp(isys->fwcom, IPU6_BASE_MSG_RECV_QUEUES);
++	if (!resp)
++		return 1;
++
++	ts = (u64)resp->timestamp[1] << 32 | resp->timestamp[0];
++
++	index = resp_type_to_index(resp->type);
++	dev_dbg(&adev->auxdev.dev,
++		"FW resp %02d %s, stream %u, ts 0x%16.16llx, pin %d\n",
++		resp->type, fw_msg[index].msg, resp->stream_handle,
++		fw_msg[index].valid_ts ? ts : 0, resp->pin_id);
++
++	if (resp->error_info.error == IPU6_FW_ISYS_ERROR_STREAM_IN_SUSPENSION)
++		/* Suspension is kind of special case: not enough buffers */
++		dev_dbg(&adev->auxdev.dev,
++			"FW error resp SUSPENSION, details %d\n",
++			resp->error_info.error_details);
++	else if (resp->error_info.error)
++		dev_dbg(&adev->auxdev.dev,
++			"FW error resp error %d, details %d\n",
++			resp->error_info.error, resp->error_info.error_details);
++
++	if (resp->stream_handle >= IPU6_ISYS_MAX_STREAMS) {
++		dev_err(&adev->auxdev.dev, "bad stream handle %u\n",
++			resp->stream_handle);
++		goto leave;
++	}
++
++	stream = ipu6_isys_query_stream_by_handle(isys, resp->stream_handle);
++	if (!stream) {
++		dev_err(&adev->auxdev.dev, "stream of stream_handle %u is unused\n",
++			resp->stream_handle);
++		goto leave;
++	}
++	stream->error = resp->error_info.error;
++
++	csi2 = ipu6_isys_subdev_to_csi2(stream->asd);
++
++	switch (resp->type) {
++	case IPU6_FW_ISYS_RESP_TYPE_STREAM_OPEN_DONE:
++		complete(&stream->stream_open_completion);
++		break;
++	case IPU6_FW_ISYS_RESP_TYPE_STREAM_CLOSE_ACK:
++		complete(&stream->stream_close_completion);
++		break;
++	case IPU6_FW_ISYS_RESP_TYPE_STREAM_START_ACK:
++		complete(&stream->stream_start_completion);
++		break;
++	case IPU6_FW_ISYS_RESP_TYPE_STREAM_START_AND_CAPTURE_ACK:
++		complete(&stream->stream_start_completion);
++		break;
++	case IPU6_FW_ISYS_RESP_TYPE_STREAM_STOP_ACK:
++		complete(&stream->stream_stop_completion);
++		break;
++	case IPU6_FW_ISYS_RESP_TYPE_STREAM_FLUSH_ACK:
++		complete(&stream->stream_stop_completion);
++		break;
++	case IPU6_FW_ISYS_RESP_TYPE_PIN_DATA_READY:
++		/*
++		 * firmware only release the capture msg until software
++		 * get pin_data_ready event
++		 */
++		ipu6_put_fw_msg_buf(ipu6_bus_get_drvdata(adev), resp->buf_id);
++		if (resp->pin_id < IPU6_ISYS_OUTPUT_PINS &&
++		    stream->output_pins[resp->pin_id].pin_ready)
++			stream->output_pins[resp->pin_id].pin_ready(stream,
++								    resp);
++		else
++			dev_warn(&adev->auxdev.dev,
++				 "%d:No data pin ready handler for pin id %d\n",
++				 resp->stream_handle, resp->pin_id);
++		if (csi2)
++			ipu6_isys_csi2_error(csi2);
++
++		break;
++	case IPU6_FW_ISYS_RESP_TYPE_STREAM_CAPTURE_ACK:
++		break;
++	case IPU6_FW_ISYS_RESP_TYPE_STREAM_START_AND_CAPTURE_DONE:
++	case IPU6_FW_ISYS_RESP_TYPE_STREAM_CAPTURE_DONE:
++		break;
++	case IPU6_FW_ISYS_RESP_TYPE_FRAME_SOF:
++
++		ipu6_isys_csi2_sof_event_by_stream(stream);
++		stream->seq[stream->seq_index].sequence =
++			atomic_read(&stream->sequence) - 1;
++		stream->seq[stream->seq_index].timestamp = ts;
++		dev_dbg(&adev->auxdev.dev,
++			"sof: handle %d: (index %u), timestamp 0x%16.16llx\n",
++			resp->stream_handle,
++			stream->seq[stream->seq_index].sequence, ts);
++		stream->seq_index = (stream->seq_index + 1)
++			% IPU6_ISYS_MAX_PARALLEL_SOF;
++		break;
++	case IPU6_FW_ISYS_RESP_TYPE_FRAME_EOF:
++		ipu6_isys_csi2_eof_event_by_stream(stream);
++		dev_dbg(&adev->auxdev.dev,
++			"eof: handle %d: (index %u), timestamp 0x%16.16llx\n",
++			resp->stream_handle,
++			stream->seq[stream->seq_index].sequence, ts);
++		break;
++	case IPU6_FW_ISYS_RESP_TYPE_STATS_DATA_READY:
++		break;
++	default:
++		dev_err(&adev->auxdev.dev, "%d:unknown response type %u\n",
++			resp->stream_handle, resp->type);
++		break;
++	}
++
++	ipu6_isys_put_stream(stream);
++leave:
++	ipu6_fw_isys_put_resp(isys->fwcom, IPU6_BASE_MSG_RECV_QUEUES);
++	return 0;
++}
++
++static const struct ipu6_auxdrv_data ipu6_isys_auxdrv_data = {
++	.isr = isys_isr,
++	.isr_threaded = NULL,
++	.wake_isr_thread = false,
++};
++
++static const struct auxiliary_device_id ipu6_isys_id_table[] = {
++	{
++		.name = "intel_ipu6.isys",
++		.driver_data = (kernel_ulong_t)&ipu6_isys_auxdrv_data,
++	},
++	{ }
++};
++MODULE_DEVICE_TABLE(auxiliary, ipu6_isys_id_table);
++
++static struct auxiliary_driver isys_driver = {
++	.name = IPU6_ISYS_NAME,
++	.probe = isys_probe,
++	.remove = isys_remove,
++	.id_table = ipu6_isys_id_table,
++	.driver = {
++		.pm = &isys_pm_ops,
++	},
++};
++
++module_auxiliary_driver(isys_driver);
++
++MODULE_AUTHOR("Sakari Ailus <sakari.ailus@linux.intel.com>");
++MODULE_AUTHOR("Tianshu Qiu <tian.shu.qiu@intel.com>");
++MODULE_AUTHOR("Bingbu Cao <bingbu.cao@intel.com>");
++MODULE_AUTHOR("Yunliang Ding <yunliang.ding@intel.com>");
++MODULE_AUTHOR("Hongju Wang <hongju.wang@intel.com>");
++MODULE_LICENSE("GPL");
++MODULE_DESCRIPTION("Intel IPU6 input system driver");
++MODULE_IMPORT_NS(INTEL_IPU6);
++MODULE_IMPORT_NS(INTEL_IPU_BRIDGE);
+diff --git a/drivers/media/pci/intel/ipu6/ipu6-isys.h b/drivers/media/pci/intel/ipu6/ipu6-isys.h
+new file mode 100644
+index 000000000000..cf7a90bfedc9
+--- /dev/null
++++ b/drivers/media/pci/intel/ipu6/ipu6-isys.h
+@@ -0,0 +1,207 @@
++/* SPDX-License-Identifier: GPL-2.0-only */
++/* Copyright (C) 2013 - 2023 Intel Corporation */
++
++#ifndef IPU6_ISYS_H
++#define IPU6_ISYS_H
++
++#include <linux/irqreturn.h>
++#include <linux/list.h>
++#include <linux/mutex.h>
++#include <linux/pm_qos.h>
++#include <linux/spinlock_types.h>
++#include <linux/types.h>
++
++#include <media/media-device.h>
++#include <media/v4l2-async.h>
++#include <media/v4l2-device.h>
++
++#include "ipu6.h"
++#include "ipu6-fw-isys.h"
++#include "ipu6-isys-csi2.h"
++#include "ipu6-isys-video.h"
++
++struct ipu6_bus_device;
++
++#define IPU6_ISYS_ENTITY_PREFIX		"Intel IPU6"
++/* FW support max 16 streams */
++#define IPU6_ISYS_MAX_STREAMS		16
++#define ISYS_UNISPART_IRQS	(IPU6_ISYS_UNISPART_IRQ_SW |	\
++				 IPU6_ISYS_UNISPART_IRQ_CSI0 |	\
++				 IPU6_ISYS_UNISPART_IRQ_CSI1)
++
++#define IPU6_ISYS_2600_MEM_LINE_ALIGN	64
++
++/*
++ * Current message queue configuration. These must be big enough
++ * so that they never gets full. Queues are located in system memory
++ */
++#define IPU6_ISYS_SIZE_RECV_QUEUE 40
++#define IPU6_ISYS_SIZE_SEND_QUEUE 40
++#define IPU6_ISYS_SIZE_PROXY_RECV_QUEUE 5
++#define IPU6_ISYS_SIZE_PROXY_SEND_QUEUE 5
++#define IPU6_ISYS_NUM_RECV_QUEUE 1
++
++#define IPU6_ISYS_MIN_WIDTH		1U
++#define IPU6_ISYS_MIN_HEIGHT		1U
++#define IPU6_ISYS_MAX_WIDTH		4672U
++#define IPU6_ISYS_MAX_HEIGHT		3416U
++
++/* the threshold granularity is 2KB on IPU6 */
++#define IPU6_SRAM_GRANULARITY_SHIFT	11
++#define IPU6_SRAM_GRANULARITY_SIZE	2048
++/* the threshold granularity is 1KB on IPU6SE */
++#define IPU6SE_SRAM_GRANULARITY_SHIFT	10
++#define IPU6SE_SRAM_GRANULARITY_SIZE	1024
++/* IS pixel buffer is 256KB, MaxSRAMSize is 200KB on IPU6 */
++#define IPU6_MAX_SRAM_SIZE			(200 << 10)
++/* IS pixel buffer is 128KB, MaxSRAMSize is 96KB on IPU6SE */
++#define IPU6SE_MAX_SRAM_SIZE			(96 << 10)
++
++#define IPU6EP_LTR_VALUE			200
++#define IPU6EP_MIN_MEMOPEN_TH			0x4
++#define IPU6EP_MTL_LTR_VALUE			1023
++#define IPU6EP_MTL_MIN_MEMOPEN_TH		0xc
++
++struct ltr_did {
++	union {
++		u32 value;
++		struct {
++			u8 val0;
++			u8 val1;
++			u8 val2;
++			u8 val3;
++		} bits;
++	} lut_ltr;
++	union {
++		u32 value;
++		struct {
++			u8 th0;
++			u8 th1;
++			u8 th2;
++			u8 th3;
++		} bits;
++	} lut_fill_time;
++};
++
++struct isys_iwake_watermark {
++	bool iwake_enabled;
++	bool force_iwake_disable;
++	u32 iwake_threshold;
++	u64 isys_pixelbuffer_datarate;
++	struct ltr_did ltrdid;
++	struct mutex mutex; /* protect whole struct */
++	struct ipu6_isys *isys;
++	struct list_head video_list;
++};
++
++struct ipu6_isys_csi2_config {
++	u32 nlanes;
++	u32 port;
++};
++
++struct sensor_async_sd {
++	struct v4l2_async_connection asc;
++	struct ipu6_isys_csi2_config csi2;
++};
++
++/*
++ * struct ipu6_isys
++ *
++ * @media_dev: Media device
++ * @v4l2_dev: V4L2 device
++ * @adev: ISYS bus device
++ * @power: Is ISYS powered on or not?
++ * @isr_bits: Which bits does the ISR handle?
++ * @power_lock: Serialise access to power (power state in general)
++ * @csi2_rx_ctrl_cached: cached shared value between all CSI2 receivers
++ * @streams_lock: serialise access to streams
++ * @streams: streams per firmware stream ID
++ * @fwcom: fw communication layer private pointer
++ *         or optional external library private pointer
++ * @line_align: line alignment in memory
++ * @phy_termcal_val: the termination calibration value, only used for DWC PHY
++ * @need_reset: Isys requires d0i0->i3 transition
++ * @ref_count: total number of callers fw open
++ * @mutex: serialise access isys video open/release related operations
++ * @stream_mutex: serialise stream start and stop, queueing requests
++ * @pdata: platform data pointer
++ * @csi2: CSI-2 receivers
++ */
++struct ipu6_isys {
++	struct media_device media_dev;
++	struct v4l2_device v4l2_dev;
++	struct ipu6_bus_device *adev;
++
++	int power;
++	spinlock_t power_lock;
++	u32 isr_csi2_bits;
++	u32 csi2_rx_ctrl_cached;
++	spinlock_t streams_lock;
++	struct ipu6_isys_stream streams[IPU6_ISYS_MAX_STREAMS];
++	int streams_ref_count[IPU6_ISYS_MAX_STREAMS];
++	void *fwcom;
++	unsigned int line_align;
++	u32 phy_termcal_val;
++	bool need_reset;
++	bool icache_prefetch;
++	bool csi2_cse_ipc_not_supported;
++	unsigned int ref_count;
++	unsigned int stream_opened;
++	unsigned int sensor_type;
++
++	struct mutex mutex;
++	struct mutex stream_mutex;
++
++	struct ipu6_isys_pdata *pdata;
++
++	int (*phy_set_power)(struct ipu6_isys *isys,
++			     struct ipu6_isys_csi2_config *cfg,
++			     const struct ipu6_isys_csi2_timing *timing,
++			     bool on);
++
++	struct ipu6_isys_csi2 *csi2;
++	struct ipu6_isys_video av[NR_OF_VIDEO_DEVICE];
++
++	struct pm_qos_request pm_qos;
++	spinlock_t listlock;	/* Protect framebuflist */
++	struct list_head framebuflist;
++	struct list_head framebuflist_fw;
++	struct v4l2_async_notifier notifier;
++	struct isys_iwake_watermark iwake_watermark;
++};
++
++struct isys_fw_msgs {
++	union {
++		u64 dummy;
++		struct ipu6_fw_isys_frame_buff_set_abi frame;
++		struct ipu6_fw_isys_stream_cfg_data_abi stream;
++	} fw_msg;
++	struct list_head head;
++	dma_addr_t dma_addr;
++};
++
++struct isys_fw_msgs *ipu6_get_fw_msg_buf(struct ipu6_isys_stream *stream);
++void ipu6_put_fw_msg_buf(struct ipu6_isys *isys, u64 data);
++void ipu6_cleanup_fw_msg_bufs(struct ipu6_isys *isys);
++
++extern const struct v4l2_ioctl_ops ipu6_isys_ioctl_ops;
++
++void isys_setup_hw(struct ipu6_isys *isys);
++irqreturn_t isys_isr(struct ipu6_bus_device *adev);
++void update_watermark_setting(struct ipu6_isys *isys);
++
++int ipu6_isys_mcd_phy_set_power(struct ipu6_isys *isys,
++				struct ipu6_isys_csi2_config *cfg,
++				const struct ipu6_isys_csi2_timing *timing,
++				bool on);
++
++int ipu6_isys_dwc_phy_set_power(struct ipu6_isys *isys,
++				struct ipu6_isys_csi2_config *cfg,
++				const struct ipu6_isys_csi2_timing *timing,
++				bool on);
++
++int ipu6_isys_jsl_phy_set_power(struct ipu6_isys *isys,
++				struct ipu6_isys_csi2_config *cfg,
++				const struct ipu6_isys_csi2_timing *timing,
++				bool on);
++#endif /* IPU6_ISYS_H */
+-- 
+2.43.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&#45;subdev0</text>
++<polyline fill="none" stroke="black" points="328,-0.5 328,-184.5 "/>
++<text text-anchor="middle" x="339.5" y="-169.3" font-family="Times,serif" font-size="14.00">1</text>
++<polyline fill="none" stroke="black" points="328,-161.5 351,-161.5 "/>
++<text text-anchor="middle" x="339.5" y="-146.3" font-family="Times,serif" font-size="14.00">2</text>
++<polyline fill="none" stroke="black" points="328,-138.5 351,-138.5 "/>
++<text text-anchor="middle" x="339.5" y="-123.3" font-family="Times,serif" font-size="14.00">3</text>
++<polyline fill="none" stroke="black" points="328,-115.5 351,-115.5 "/>
++<text text-anchor="middle" x="339.5" y="-100.3" font-family="Times,serif" font-size="14.00">4</text>
++<polyline fill="none" stroke="black" points="328,-92.5 351,-92.5 "/>
++<text text-anchor="middle" x="339.5" y="-77.3" font-family="Times,serif" font-size="14.00">5</text>
++<polyline fill="none" stroke="black" points="328,-69.5 351,-69.5 "/>
++<text text-anchor="middle" x="339.5" y="-54.3" font-family="Times,serif" font-size="14.00">6</text>
++<polyline fill="none" stroke="black" points="328,-46.5 351,-46.5 "/>
++<text text-anchor="middle" x="339.5" y="-31.3" font-family="Times,serif" font-size="14.00">7</text>
++<polyline fill="none" stroke="black" points="328,-23.5 351,-23.5 "/>
++<text text-anchor="middle" x="339.5" y="-8.3" font-family="Times,serif" font-size="14.00">8</text>
++</g>
++<!-- n0000007d&#45;&gt;n00000001 -->
++<g id="edge1" class="edge"><title>n0000007d:port1&#45;&gt;n00000001</title>
++<path fill="none" stroke="black" stroke-dasharray="5,2" d="M351,-173.5C359.322,-173.5 367.976,-173.5 376.644,-173.5"/>
++<polygon fill="black" stroke="black" points="376.807,-177 386.807,-173.5 376.807,-170 376.807,-177"/>
++</g>
++<!-- n00000087 -->
++<g id="node6" class="node"><title>n00000087</title>
++<path fill="#ffb90f" stroke="black" d="M201,-203.5C201,-203.5 339,-203.5 339,-203.5 345,-203.5 351,-209.5 351,-215.5 351,-215.5 351,-375.5 351,-375.5 351,-381.5 345,-387.5 339,-387.5 339,-387.5 201,-387.5 201,-387.5 195,-387.5 189,-381.5 189,-375.5 189,-375.5 189,-215.5 189,-215.5 189,-209.5 195,-203.5 201,-203.5"/>
++<text text-anchor="middle" x="200.5" y="-291.8" font-family="Times,serif" font-size="14.00">0</text>
++<polyline fill="none" stroke="black" points="212,-203.5 212,-387.5 "/>
++<text text-anchor="middle" x="270" y="-299.3" font-family="Times,serif" font-size="14.00">Intel IPU6 CSI2 1</text>
++<text text-anchor="middle" x="270" y="-284.3" font-family="Times,serif" font-size="14.00">/dev/v4l&#45;subdev1</text>
++<polyline fill="none" stroke="black" points="328,-203.5 328,-387.5 "/>
++<text text-anchor="middle" x="339.5" y="-372.3" font-family="Times,serif" font-size="14.00">1</text>
++<polyline fill="none" stroke="black" points="328,-364.5 351,-364.5 "/>
++<text text-anchor="middle" x="339.5" y="-349.3" font-family="Times,serif" font-size="14.00">2</text>
++<polyline fill="none" stroke="black" points="328,-341.5 351,-341.5 "/>
++<text text-anchor="middle" x="339.5" y="-326.3" font-family="Times,serif" font-size="14.00">3</text>
++<polyline fill="none" stroke="black" points="328,-318.5 351,-318.5 "/>
++<text text-anchor="middle" x="339.5" y="-303.3" font-family="Times,serif" font-size="14.00">4</text>
++<polyline fill="none" stroke="black" points="328,-295.5 351,-295.5 "/>
++<text text-anchor="middle" x="339.5" y="-280.3" font-family="Times,serif" font-size="14.00">5</text>
++<polyline fill="none" stroke="black" points="328,-272.5 351,-272.5 "/>
++<text text-anchor="middle" x="339.5" y="-257.3" font-family="Times,serif" font-size="14.00">6</text>
++<polyline fill="none" stroke="black" points="328,-249.5 351,-249.5 "/>
++<text text-anchor="middle" x="339.5" y="-234.3" font-family="Times,serif" font-size="14.00">7</text>
++<polyline fill="none" stroke="black" points="328,-226.5 351,-226.5 "/>
++<text text-anchor="middle" x="339.5" y="-211.3" font-family="Times,serif" font-size="14.00">8</text>
++</g>
++<!-- n00000087&#45;&gt;n00000002 -->
++<g id="edge2" class="edge"><title>n00000087:port1&#45;&gt;n00000002</title>
++<path fill="none" stroke="black" stroke-dasharray="5,2" d="M351,-376.5C359.322,-376.5 367.976,-376.5 376.644,-376.5"/>
++<polygon fill="black" stroke="black" points="376.807,-380 386.807,-376.5 376.807,-373 376.807,-380"/>
++</g>
++<!-- n00000091 -->
++<g id="node7" class="node"><title>n00000091</title>
++<path fill="#ffb90f" stroke="black" d="M201,-406.5C201,-406.5 339,-406.5 339,-406.5 345,-406.5 351,-412.5 351,-418.5 351,-418.5 351,-578.5 351,-578.5 351,-584.5 345,-590.5 339,-590.5 339,-590.5 201,-590.5 201,-590.5 195,-590.5 189,-584.5 189,-578.5 189,-578.5 189,-418.5 189,-418.5 189,-412.5 195,-406.5 201,-406.5"/>
++<text text-anchor="middle" x="200.5" y="-494.8" font-family="Times,serif" font-size="14.00">0</text>
++<polyline fill="none" stroke="black" points="212,-406.5 212,-590.5 "/>
++<text text-anchor="middle" x="270" y="-502.3" font-family="Times,serif" font-size="14.00">Intel IPU6 CSI2 2</text>
++<text text-anchor="middle" x="270" y="-487.3" font-family="Times,serif" font-size="14.00">/dev/v4l&#45;subdev2</text>
++<polyline fill="none" stroke="black" points="328,-406.5 328,-590.5 "/>
++<text text-anchor="middle" x="339.5" y="-575.3" font-family="Times,serif" font-size="14.00">1</text>
++<polyline fill="none" stroke="black" points="328,-567.5 351,-567.5 "/>
++<text text-anchor="middle" x="339.5" y="-552.3" font-family="Times,serif" font-size="14.00">2</text>
++<polyline fill="none" stroke="black" points="328,-544.5 351,-544.5 "/>
++<text text-anchor="middle" x="339.5" y="-529.3" font-family="Times,serif" font-size="14.00">3</text>
++<polyline fill="none" stroke="black" points="328,-521.5 351,-521.5 "/>
++<text text-anchor="middle" x="339.5" y="-506.3" font-family="Times,serif" font-size="14.00">4</text>
++<polyline fill="none" stroke="black" points="328,-498.5 351,-498.5 "/>
++<text text-anchor="middle" x="339.5" y="-483.3" font-family="Times,serif" font-size="14.00">5</text>
++<polyline fill="none" stroke="black" points="328,-475.5 351,-475.5 "/>
++<text text-anchor="middle" x="339.5" y="-460.3" font-family="Times,serif" font-size="14.00">6</text>
++<polyline fill="none" stroke="black" points="328,-452.5 351,-452.5 "/>
++<text text-anchor="middle" x="339.5" y="-437.3" font-family="Times,serif" font-size="14.00">7</text>
++<polyline fill="none" stroke="black" points="328,-429.5 351,-429.5 "/>
++<text text-anchor="middle" x="339.5" y="-414.3" font-family="Times,serif" font-size="14.00">8</text>
++</g>
++<!-- n00000091&#45;&gt;n00000003 -->
++<g id="edge3" class="edge"><title>n00000091:port1&#45;&gt;n00000003</title>
++<path fill="none" stroke="black" d="M351,-579.5C359.322,-579.5 367.976,-579.5 376.644,-579.5"/>
++<polygon fill="black" stroke="black" points="376.807,-583 386.807,-579.5 376.807,-576 376.807,-583"/>
++</g>
++<!-- n0000009b -->
++<g id="node8" class="node"><title>n0000009b</title>
++<path fill="#ffb90f" stroke="black" d="M201,-609.5C201,-609.5 339,-609.5 339,-609.5 345,-609.5 351,-615.5 351,-621.5 351,-621.5 351,-781.5 351,-781.5 351,-787.5 345,-793.5 339,-793.5 339,-793.5 201,-793.5 201,-793.5 195,-793.5 189,-787.5 189,-781.5 189,-781.5 189,-621.5 189,-621.5 189,-615.5 195,-609.5 201,-609.5"/>
++<text text-anchor="middle" x="200.5" y="-697.8" font-family="Times,serif" font-size="14.00">0</text>
++<polyline fill="none" stroke="black" points="212,-609.5 212,-793.5 "/>
++<text text-anchor="middle" x="270" y="-705.3" font-family="Times,serif" font-size="14.00">Intel IPU6 CSI2 3</text>
++<text text-anchor="middle" x="270" y="-690.3" font-family="Times,serif" font-size="14.00">/dev/v4l&#45;subdev3</text>
++<polyline fill="none" stroke="black" points="328,-609.5 328,-793.5 "/>
++<text text-anchor="middle" x="339.5" y="-778.3" font-family="Times,serif" font-size="14.00">1</text>
++<polyline fill="none" stroke="black" points="328,-770.5 351,-770.5 "/>
++<text text-anchor="middle" x="339.5" y="-755.3" font-family="Times,serif" font-size="14.00">2</text>
++<polyline fill="none" stroke="black" points="328,-747.5 351,-747.5 "/>
++<text text-anchor="middle" x="339.5" y="-732.3" font-family="Times,serif" font-size="14.00">3</text>
++<polyline fill="none" stroke="black" points="328,-724.5 351,-724.5 "/>
++<text text-anchor="middle" x="339.5" y="-709.3" font-family="Times,serif" font-size="14.00">4</text>
++<polyline fill="none" stroke="black" points="328,-701.5 351,-701.5 "/>
++<text text-anchor="middle" x="339.5" y="-686.3" font-family="Times,serif" font-size="14.00">5</text>
++<polyline fill="none" stroke="black" points="328,-678.5 351,-678.5 "/>
++<text text-anchor="middle" x="339.5" y="-663.3" font-family="Times,serif" font-size="14.00">6</text>
++<polyline fill="none" stroke="black" points="328,-655.5 351,-655.5 "/>
++<text text-anchor="middle" x="339.5" y="-640.3" font-family="Times,serif" font-size="14.00">7</text>
++<polyline fill="none" stroke="black" points="328,-632.5 351,-632.5 "/>
++<text text-anchor="middle" x="339.5" y="-617.3" font-family="Times,serif" font-size="14.00">8</text>
++</g>
++<!-- n0000009b&#45;&gt;n00000004 -->
++<g id="edge4" class="edge"><title>n0000009b:port1&#45;&gt;n00000004</title>
++<path fill="none" stroke="black" stroke-dasharray="5,2" d="M351,-782.5C359.322,-782.5 367.976,-782.5 376.644,-782.5"/>
++<polygon fill="black" stroke="black" points="376.807,-786 386.807,-782.5 376.807,-779 376.807,-786"/>
++</g>
++<!-- n00000865 -->
++<g id="node9" class="node"><title>n00000865</title>
++<path fill="cornflowerblue" stroke="black" d="M12,-479.5C12,-479.5 141,-479.5 141,-479.5 147,-479.5 153,-485.5 153,-491.5 153,-491.5 153,-505.5 153,-505.5 153,-511.5 147,-517.5 141,-517.5 141,-517.5 12,-517.5 12,-517.5 6,-517.5 0,-511.5 0,-505.5 0,-505.5 0,-491.5 0,-491.5 0,-485.5 6,-479.5 12,-479.5"/>
++<text text-anchor="middle" x="10" y="-494.8" font-family="Times,serif" font-size="14.00"> </text>
++<polyline fill="none" stroke="black" points="20,-479.5 20,-517.5 "/>
++<text text-anchor="middle" x="75" y="-502.3" font-family="Times,serif" font-size="14.00">ov01a10 3&#45;0036</text>
++<text text-anchor="middle" x="75" y="-487.3" font-family="Times,serif" font-size="14.00">/dev/v4l&#45;subdev4</text>
++<polyline fill="none" stroke="black" points="130,-479.5 130,-517.5 "/>
++<text text-anchor="middle" x="141.5" y="-494.8" font-family="Times,serif" font-size="14.00">0</text>
++</g>
++<!-- n00000865&#45;&gt;n00000091 -->
++<g id="edge5" class="edge"><title>n00000865:port0&#45;&gt;n00000091:port0</title>
++<path fill="none" stroke="black" d="M153,-498.5C165,-498.5 170.25,-498.5 178.875,-498.5"/>
++<polygon fill="black" stroke="black" points="179,-502 189,-498.5 179,-495 179,-502"/>
++</g>
++<!-- n00000866 -->
++<!-- n00000866&#45;&gt;n0000007d -->
++<!-- n00000867 -->
++<!-- n00000867&#45;&gt;n00000087 -->
++<!-- n00000868 -->
++<!-- n00000868&#45;&gt;n0000009b -->
++</g>
++</svg>
+diff --git a/Documentation/admin-guide/media/v4l-drivers.rst b/Documentation/admin-guide/media/v4l-drivers.rst
+index 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
+