dmaengine.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH v2 0/3] Digiteq Automotive MGB4 driver
@ 2022-09-19 18:55 tumic
  2022-09-19 18:55 ` [PATCH v2 1/3] Added platform module alias for the xiic I2C driver tumic
                   ` (3 more replies)
  0 siblings, 4 replies; 13+ messages in thread
From: tumic @ 2022-09-19 18:55 UTC (permalink / raw)
  To: Mauro Carvalho Chehab, Vinod Koul, Michal Simek
  Cc: linux-media, linux-kernel, dmaengine, linux-i2c, Martin Tůma

From: Martin Tůma <martin.tuma@digiteqautomotive.com>

Hi,
This series of patches adds a driver for the Digiteq Automotive MGB4 grabber
card. MGB4 is a modular frame grabber PCIe card for automotive video interfaces
(FPD-Link and GMSL for now). It is based on a Xilinx FPGA and uses their
XDMA IP core for DMA transfers. Additionally, Xilinx I2C and SPI IP cores
which already have drivers in linux are used in the design.

Except of the required xiic driver alias, the patches are split into two parts:
the XDMA driver and a "standard" v4l2 device driver. The XDMA driver is
originally based on Xilinx's sample code that can be found at:
https://github.com/Xilinx/dma_ip_drivers

The rest is a quite standard v4l2 driver, with one exception - there are
a lot of sysfs options that may/must be set before opening the v4l2 device
to adapt the card on a specific signal (see mgb4-sysfs.rst for details)
as the card must be able to work with various signal sources (or displays)
that can not be auto-detected.

I have run the driver through the v4l2-compliance test suite for both the
input and the output and the results look fine to me (I can provide the
output if required).

Changes in v2:
* Completely rewritten the original Xilinx's XDMA driver to meet kernel code
  standards.
* Added all required "to" and "cc" mail addresses.

Martin Tůma (3):
  Added platform module alias for the xiic I2C driver
  Added Xilinx XDMA IP core driver
  Added Digiteq Automotive MGB4 driver

 Documentation/admin-guide/media/mgb4-iio.rst  |   30 +
 Documentation/admin-guide/media/mgb4-mtd.rst  |   16 +
 .../admin-guide/media/mgb4-sysfs.rst          |  297 +++
 drivers/dma/Kconfig                           |    7 +
 drivers/dma/xilinx/Makefile                   |    1 +
 drivers/dma/xilinx/xilinx_xdma.c              | 2042 +++++++++++++++++
 drivers/i2c/busses/i2c-xiic.c                 |    1 +
 drivers/media/pci/Kconfig                     |    1 +
 drivers/media/pci/Makefile                    |    1 +
 drivers/media/pci/mgb4/Kconfig                |   17 +
 drivers/media/pci/mgb4/Makefile               |    6 +
 drivers/media/pci/mgb4/mgb4_cmt.c             |  243 ++
 drivers/media/pci/mgb4/mgb4_cmt.h             |   16 +
 drivers/media/pci/mgb4/mgb4_core.c            |  554 +++++
 drivers/media/pci/mgb4/mgb4_core.h            |   58 +
 drivers/media/pci/mgb4/mgb4_i2c.c             |  139 ++
 drivers/media/pci/mgb4/mgb4_i2c.h             |   35 +
 drivers/media/pci/mgb4/mgb4_io.h              |   36 +
 drivers/media/pci/mgb4/mgb4_regs.c            |   30 +
 drivers/media/pci/mgb4/mgb4_regs.h            |   35 +
 drivers/media/pci/mgb4/mgb4_sysfs.h           |   18 +
 drivers/media/pci/mgb4/mgb4_sysfs_in.c        |  750 ++++++
 drivers/media/pci/mgb4/mgb4_sysfs_out.c       |  734 ++++++
 drivers/media/pci/mgb4/mgb4_sysfs_pci.c       |   83 +
 drivers/media/pci/mgb4/mgb4_trigger.c         |  202 ++
 drivers/media/pci/mgb4/mgb4_trigger.h         |    8 +
 drivers/media/pci/mgb4/mgb4_vin.c             |  656 ++++++
 drivers/media/pci/mgb4/mgb4_vin.h             |   64 +
 drivers/media/pci/mgb4/mgb4_vout.c            |  502 ++++
 drivers/media/pci/mgb4/mgb4_vout.h            |   58 +
 include/linux/dma/xilinx_xdma.h               |   44 +
 31 files changed, 6684 insertions(+)
 create mode 100644 Documentation/admin-guide/media/mgb4-iio.rst
 create mode 100644 Documentation/admin-guide/media/mgb4-mtd.rst
 create mode 100644 Documentation/admin-guide/media/mgb4-sysfs.rst
 create mode 100644 drivers/dma/xilinx/xilinx_xdma.c
 create mode 100644 drivers/media/pci/mgb4/Kconfig
 create mode 100644 drivers/media/pci/mgb4/Makefile
 create mode 100644 drivers/media/pci/mgb4/mgb4_cmt.c
 create mode 100644 drivers/media/pci/mgb4/mgb4_cmt.h
 create mode 100644 drivers/media/pci/mgb4/mgb4_core.c
 create mode 100644 drivers/media/pci/mgb4/mgb4_core.h
 create mode 100644 drivers/media/pci/mgb4/mgb4_i2c.c
 create mode 100644 drivers/media/pci/mgb4/mgb4_i2c.h
 create mode 100644 drivers/media/pci/mgb4/mgb4_io.h
 create mode 100644 drivers/media/pci/mgb4/mgb4_regs.c
 create mode 100644 drivers/media/pci/mgb4/mgb4_regs.h
 create mode 100644 drivers/media/pci/mgb4/mgb4_sysfs.h
 create mode 100644 drivers/media/pci/mgb4/mgb4_sysfs_in.c
 create mode 100644 drivers/media/pci/mgb4/mgb4_sysfs_out.c
 create mode 100644 drivers/media/pci/mgb4/mgb4_sysfs_pci.c
 create mode 100644 drivers/media/pci/mgb4/mgb4_trigger.c
 create mode 100644 drivers/media/pci/mgb4/mgb4_trigger.h
 create mode 100644 drivers/media/pci/mgb4/mgb4_vin.c
 create mode 100644 drivers/media/pci/mgb4/mgb4_vin.h
 create mode 100644 drivers/media/pci/mgb4/mgb4_vout.c
 create mode 100644 drivers/media/pci/mgb4/mgb4_vout.h
 create mode 100644 include/linux/dma/xilinx_xdma.h

-- 
2.37.2


^ permalink raw reply	[flat|nested] 13+ messages in thread

* [PATCH v2 1/3] Added platform module alias for the xiic I2C driver
  2022-09-19 18:55 [PATCH v2 0/3] Digiteq Automotive MGB4 driver tumic
@ 2022-09-19 18:55 ` tumic
  2022-09-20 14:30   ` Michal Simek
  2022-09-19 18:55 ` [PATCH v2 2/3] Added Xilinx XDMA IP core driver tumic
                   ` (2 subsequent siblings)
  3 siblings, 1 reply; 13+ messages in thread
From: tumic @ 2022-09-19 18:55 UTC (permalink / raw)
  To: Mauro Carvalho Chehab, Vinod Koul, Michal Simek
  Cc: linux-media, linux-kernel, dmaengine, linux-i2c, Martin Tůma

From: Martin Tůma <martin.tuma@digiteqautomotive.com>

The missing "platform" alias is required for the mgb4 v4l2 driver to load
the i2c controller driver when probing the HW.

Signed-off-by: Martin Tůma <martin.tuma@digiteqautomotive.com>
---
 drivers/i2c/busses/i2c-xiic.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/drivers/i2c/busses/i2c-xiic.c b/drivers/i2c/busses/i2c-xiic.c
index b3fe6b2aa3ca..277a02455cdd 100644
--- a/drivers/i2c/busses/i2c-xiic.c
+++ b/drivers/i2c/busses/i2c-xiic.c
@@ -920,6 +920,7 @@ static struct platform_driver xiic_i2c_driver = {
 
 module_platform_driver(xiic_i2c_driver);
 
+MODULE_ALIAS("platform:" DRIVER_NAME);
 MODULE_AUTHOR("info@mocean-labs.com");
 MODULE_DESCRIPTION("Xilinx I2C bus driver");
 MODULE_LICENSE("GPL v2");
-- 
2.37.2


^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [PATCH v2 2/3] Added Xilinx XDMA IP core driver
  2022-09-19 18:55 [PATCH v2 0/3] Digiteq Automotive MGB4 driver tumic
  2022-09-19 18:55 ` [PATCH v2 1/3] Added platform module alias for the xiic I2C driver tumic
@ 2022-09-19 18:55 ` tumic
  2022-09-20  2:12   ` kernel test robot
  2022-09-21  6:24   ` Lizhi Hou
  2022-09-19 18:55 ` [PATCH v2 3/3] Added Digiteq Automotive MGB4 driver tumic
  2022-09-21  5:32 ` [PATCH v2 0/3] " Sonal Santan
  3 siblings, 2 replies; 13+ messages in thread
From: tumic @ 2022-09-19 18:55 UTC (permalink / raw)
  To: Mauro Carvalho Chehab, Vinod Koul, Michal Simek
  Cc: linux-media, linux-kernel, dmaengine, linux-i2c, Martin Tůma

From: Martin Tůma <martin.tuma@digiteqautomotive.com>

Added support for the Xilinx XDMA PCIe DMA IP core. The XDMA IP core is used in
many FPGA PCIe card designs for DMA transfers between the PCIe card and the host
system. This driver can be incorporated into any PCIe card (that contains
the XDMA IP core) driver to initialize the XDMA HW and process DMA transfers.

The driver is originally based on the code provided by Xilinx at
https://github.com/Xilinx/dma_ip_drivers

Signed-off-by: Martin Tůma <martin.tuma@digiteqautomotive.com>
---
 drivers/dma/Kconfig              |    7 +
 drivers/dma/xilinx/Makefile      |    1 +
 drivers/dma/xilinx/xilinx_xdma.c | 2042 ++++++++++++++++++++++++++++++
 include/linux/dma/xilinx_xdma.h  |   44 +
 4 files changed, 2094 insertions(+)
 create mode 100644 drivers/dma/xilinx/xilinx_xdma.c
 create mode 100644 include/linux/dma/xilinx_xdma.h

diff --git a/drivers/dma/Kconfig b/drivers/dma/Kconfig
index a06d2a7627aa..932086cd5962 100644
--- a/drivers/dma/Kconfig
+++ b/drivers/dma/Kconfig
@@ -804,4 +804,11 @@ config DMATEST
 config DMA_ENGINE_RAID
 	bool
 
+config XILINX_XDMA
+	tristate "Xilinx XDMA Engine"
+	depends on PCI
+	select DMA_ENGINE
+	help
+	  Enable support for Xilinx XDMA IP controller.
+
 endif
diff --git a/drivers/dma/xilinx/Makefile b/drivers/dma/xilinx/Makefile
index 767bb45f641f..55e97686f8ea 100644
--- a/drivers/dma/xilinx/Makefile
+++ b/drivers/dma/xilinx/Makefile
@@ -2,3 +2,4 @@
 obj-$(CONFIG_XILINX_DMA) += xilinx_dma.o
 obj-$(CONFIG_XILINX_ZYNQMP_DMA) += zynqmp_dma.o
 obj-$(CONFIG_XILINX_ZYNQMP_DPDMA) += xilinx_dpdma.o
+obj-$(CONFIG_XILINX_XDMA) += xilinx_xdma.o
diff --git a/drivers/dma/xilinx/xilinx_xdma.c b/drivers/dma/xilinx/xilinx_xdma.c
new file mode 100644
index 000000000000..9db637c25045
--- /dev/null
+++ b/drivers/dma/xilinx/xilinx_xdma.c
@@ -0,0 +1,2042 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * This file is part of the Xilinx DMA IP Core driver for Linux
+ *
+ * Copyright (c) 2016-2021,  Xilinx, Inc.
+ * Copyright (c) 2022,       Digiteq Automotive s.r.o.
+ */
+
+#include <linux/module.h>
+#include <linux/kernel.h>
+#include <linux/string.h>
+#include <linux/mm.h>
+#include <linux/errno.h>
+#include <linux/sched.h>
+#include <linux/vmalloc.h>
+#include <linux/pci.h>
+#include <linux/dma/xilinx_xdma.h>
+
+
+static unsigned int enable_credit_mp = 1;
+module_param(enable_credit_mp, uint, 0644);
+MODULE_PARM_DESC(enable_credit_mp,
+		 "Set 0 to disable credit feature, default is 1 (enabled)");
+
+#define XDMA_BAR_SIZE	0x8000UL
+
+#define XDMA_CHANNEL_NUM_MAX	4
+#define XDMA_ENG_IRQ_NUM	1
+#define XDMA_MAX_ADJ_BLOCK_SIZE	0x40
+#define XDMA_PAGE_SIZE		0x1000
+#define RX_STATUS_EOP 1
+
+#define XDMA_OFS_INT_CTRL	0x2000UL
+#define XDMA_OFS_CONFIG		0x3000UL
+
+#define XDMA_TRANSFER_MAX_DESC	2048
+
+#define XDMA_DESC_BLEN_BITS	28
+#define XDMA_DESC_BLEN_MAX	((1 << (XDMA_DESC_BLEN_BITS)) - 1)
+
+/* bits of the SG DMA control register */
+#define XDMA_CTRL_RUN_STOP			(1UL << 0)
+#define XDMA_CTRL_IE_DESC_STOPPED		(1UL << 1)
+#define XDMA_CTRL_IE_DESC_COMPLETED		(1UL << 2)
+#define XDMA_CTRL_IE_DESC_ALIGN_MISMATCH	(1UL << 3)
+#define XDMA_CTRL_IE_MAGIC_STOPPED		(1UL << 4)
+#define XDMA_CTRL_IE_IDLE_STOPPED		(1UL << 6)
+#define XDMA_CTRL_IE_READ_ERROR			(0x1FUL << 9)
+#define XDMA_CTRL_IE_DESC_ERROR			(0x1FUL << 19)
+#define XDMA_CTRL_NON_INCR_ADDR			(1UL << 25)
+#define XDMA_CTRL_POLL_MODE_WB			(1UL << 26)
+#define XDMA_CTRL_STM_MODE_WB			(1UL << 27)
+
+/* bits of the SG DMA status register */
+#define XDMA_STAT_BUSY			(1UL << 0)
+#define XDMA_STAT_DESC_STOPPED		(1UL << 1)
+#define XDMA_STAT_DESC_COMPLETED	(1UL << 2)
+#define XDMA_STAT_ALIGN_MISMATCH	(1UL << 3)
+#define XDMA_STAT_MAGIC_STOPPED		(1UL << 4)
+#define XDMA_STAT_INVALID_LEN		(1UL << 5)
+#define XDMA_STAT_IDLE_STOPPED		(1UL << 6)
+
+#define XDMA_STAT_COMMON_ERR_MASK \
+	(XDMA_STAT_ALIGN_MISMATCH | XDMA_STAT_MAGIC_STOPPED | \
+	 XDMA_STAT_INVALID_LEN)
+
+/* desc_error, C2H & H2C */
+#define XDMA_STAT_DESC_UNSUPP_REQ	(1UL << 19)
+#define XDMA_STAT_DESC_COMPL_ABORT	(1UL << 20)
+#define XDMA_STAT_DESC_PARITY_ERR	(1UL << 21)
+#define XDMA_STAT_DESC_HEADER_EP	(1UL << 22)
+#define XDMA_STAT_DESC_UNEXP_COMPL	(1UL << 23)
+
+#define XDMA_STAT_DESC_ERR_MASK	\
+	(XDMA_STAT_DESC_UNSUPP_REQ | XDMA_STAT_DESC_COMPL_ABORT | \
+	 XDMA_STAT_DESC_PARITY_ERR | XDMA_STAT_DESC_HEADER_EP | \
+	 XDMA_STAT_DESC_UNEXP_COMPL)
+
+/* read error: H2C */
+#define XDMA_STAT_H2C_R_UNSUPP_REQ	(1UL << 9)
+#define XDMA_STAT_H2C_R_COMPL_ABORT	(1UL << 10)
+#define XDMA_STAT_H2C_R_PARITY_ERR	(1UL << 11)
+#define XDMA_STAT_H2C_R_HEADER_EP	(1UL << 12)
+#define XDMA_STAT_H2C_R_UNEXP_COMPL	(1UL << 13)
+
+#define XDMA_STAT_H2C_R_ERR_MASK	\
+	(XDMA_STAT_H2C_R_UNSUPP_REQ | XDMA_STAT_H2C_R_COMPL_ABORT | \
+	 XDMA_STAT_H2C_R_PARITY_ERR | XDMA_STAT_H2C_R_HEADER_EP | \
+	 XDMA_STAT_H2C_R_UNEXP_COMPL)
+
+/* write error, H2C only */
+#define XDMA_STAT_H2C_W_DECODE_ERR	(1UL << 14)
+#define XDMA_STAT_H2C_W_SLAVE_ERR	(1UL << 15)
+
+#define XDMA_STAT_H2C_W_ERR_MASK	\
+	(XDMA_STAT_H2C_W_DECODE_ERR | XDMA_STAT_H2C_W_SLAVE_ERR)
+
+/* read error: C2H */
+#define XDMA_STAT_C2H_R_DECODE_ERR	(1UL << 9)
+#define XDMA_STAT_C2H_R_SLAVE_ERR	(1UL << 10)
+
+#define XDMA_STAT_C2H_R_ERR_MASK	\
+	(XDMA_STAT_C2H_R_DECODE_ERR | XDMA_STAT_C2H_R_SLAVE_ERR)
+
+/* all combined */
+#define XDMA_STAT_H2C_ERR_MASK	\
+	(XDMA_STAT_COMMON_ERR_MASK | XDMA_STAT_DESC_ERR_MASK | \
+	 XDMA_STAT_H2C_R_ERR_MASK | XDMA_STAT_H2C_W_ERR_MASK)
+
+#define XDMA_STAT_C2H_ERR_MASK	\
+	(XDMA_STAT_COMMON_ERR_MASK | XDMA_STAT_DESC_ERR_MASK | \
+	 XDMA_STAT_C2H_R_ERR_MASK)
+
+/* bits of the SGDMA descriptor control field */
+#define XDMA_DESC_STOPPED	(1UL << 0)
+#define XDMA_DESC_COMPLETED	(1UL << 1)
+#define XDMA_DESC_EOP		(1UL << 4)
+
+/* upper 16-bits of engine identifier register */
+#define XDMA_ID_H2C 0x1fc0U
+#define XDMA_ID_C2H 0x1fc1U
+
+#define LS_BYTE_MASK 0x000000FFUL
+
+#define BLOCK_ID_MASK 0xFFF00000
+#define BLOCK_ID_HEAD 0x1FC00000
+
+#define IRQ_BLOCK_ID 0x1fc20000UL
+#define CONFIG_BLOCK_ID 0x1fc30000UL
+
+#define WB_COUNT_MASK 0x00ffffffUL
+#define WB_ERR_MASK (1UL << 31)
+
+#define MAX_USER_IRQ 16
+
+#define DESC_MAGIC 0xAD4B0000UL
+
+#define C2H_WB 0x52B4UL
+
+#define H2C_CHANNEL_OFFSET 0x1000
+#define SGDMA_OFFSET_FROM_CHANNEL 0x4000
+#define CHANNEL_SPACING 0x100
+#define TARGET_SPACING 0x1000
+
+/* obtain the 32 most significant (high) bits of a 32-bit or 64-bit address */
+#define PCI_DMA_H(addr) ((addr >> 16) >> 16)
+/* obtain the 32 least significant (low) bits of a 32-bit or 64-bit address */
+#define PCI_DMA_L(addr) (addr & 0xffffffffUL)
+
+
+enum transfer_state {
+	TRANSFER_STATE_NEW = 0,
+	TRANSFER_STATE_SUBMITTED,
+	TRANSFER_STATE_COMPLETED,
+	TRANSFER_STATE_FAILED,
+	TRANSFER_STATE_ABORTED
+};
+
+enum shutdown_state {
+	ENGINE_SHUTDOWN_NONE = 0,	/* No shutdown in progress */
+	ENGINE_SHUTDOWN_REQUEST = 1,	/* engine requested to shutdown */
+	ENGINE_SHUTDOWN_IDLE = 2	/* engine has shutdown and is idle */
+};
+
+struct config_regs {
+	u32 identifier;
+	u32 reserved_1[4];
+	u32 msi_enable;
+};
+
+struct engine_regs {
+	u32 identifier;
+	u32 control;
+	u32 control_w1s;
+	u32 control_w1c;
+	u32 reserved_1[12];	/* padding */
+
+	u32 status;
+	u32 status_rc;
+	u32 completed_desc_count;
+	u32 alignments;
+	u32 reserved_2[14];	/* padding */
+
+	u32 poll_mode_wb_lo;
+	u32 poll_mode_wb_hi;
+	u32 interrupt_enable_mask;
+	u32 interrupt_enable_mask_w1s;
+	u32 interrupt_enable_mask_w1c;
+	u32 reserved_3[9];	/* padding */
+
+	u32 perf_ctrl;
+	u32 perf_cyc_lo;
+	u32 perf_cyc_hi;
+	u32 perf_dat_lo;
+	u32 perf_dat_hi;
+	u32 perf_pnd_lo;
+	u32 perf_pnd_hi;
+} __packed;
+
+struct engine_sgdma_regs {
+	u32 identifier;
+	u32 reserved_1[31];	/* padding */
+
+	/* bus address to first descriptor in Root Complex Memory */
+	u32 first_desc_lo;
+	u32 first_desc_hi;
+	/* number of adjacent descriptors at first_desc */
+	u32 first_desc_adjacent;
+	u32 credits;
+} __packed;
+
+struct interrupt_regs {
+	u32 identifier;
+	u32 user_int_enable;
+	u32 user_int_enable_w1s;
+	u32 user_int_enable_w1c;
+	u32 channel_int_enable;
+	u32 channel_int_enable_w1s;
+	u32 channel_int_enable_w1c;
+	u32 reserved_1[9];	/* padding */
+
+	u32 user_int_request;
+	u32 channel_int_request;
+	u32 user_int_pending;
+	u32 channel_int_pending;
+	u32 reserved_2[12];	/* padding */
+
+	u32 user_msi_vector[8];
+	u32 channel_msi_vector[8];
+} __packed;
+
+struct sgdma_common_regs {
+	u32 padding[8];
+	u32 credit_mode_enable;
+	u32 credit_mode_enable_w1s;
+	u32 credit_mode_enable_w1c;
+} __packed;
+
+
+/*
+ * Descriptor for a single contiguous memory block transfer.
+ *
+ * Multiple descriptors are linked by means of the next pointer. An additional
+ * extra adjacent number gives the amount of extra contiguous descriptors.
+ *
+ * The descriptors are in root complex memory, and the bytes in the 32-bit
+ * words must be in little-endian byte ordering.
+ */
+struct xdma_desc {
+	u32 control;
+	u32 bytes;		/* transfer length in bytes */
+	u32 src_addr_lo;	/* source address (low 32-bit) */
+	u32 src_addr_hi;	/* source address (high 32-bit) */
+	u32 dst_addr_lo;	/* destination address (low 32-bit) */
+	u32 dst_addr_hi;	/* destination address (high 32-bit) */
+	/*
+	 * next descriptor in the single-linked list of descriptors;
+	 * this is the PCIe (bus) address of the next descriptor in the
+	 * root complex memory
+	 */
+	u32 next_lo;		/* next desc address (low 32-bit) */
+	u32 next_hi;		/* next desc address (high 32-bit) */
+} __packed;
+
+/* 32 bytes (four 32-bit words) or 64 bytes (eight 32-bit words) */
+struct xdma_result {
+	u32 status;
+	u32 length;
+	u32 reserved_1[6];	/* padding */
+} __packed;
+
+struct sw_desc {
+	dma_addr_t addr;
+	unsigned int len;
+};
+
+/* Describes a (SG DMA) single transfer for the engine */
+#define XFER_FLAG_NEED_UNMAP		0x1
+#define XFER_FLAG_ST_C2H_EOP_RCVED	0x2	/* ST c2h only */
+struct xdma_transfer {
+	struct list_head entry;		/* queue of non-completed transfers */
+	struct xdma_desc *desc_virt;	/* virt addr of the 1st descriptor */
+	struct xdma_result *res_virt;   /* virt addr of result, c2h streaming */
+	dma_addr_t res_bus;		/* bus addr for result descriptors */
+	dma_addr_t desc_bus;		/* bus addr of the first descriptor */
+	int desc_adjacent;		/* adjacent descriptors at desc_bus */
+	int desc_num;			/* number of descriptors in transfer */
+	int desc_index;			/* index for 1st desc. in transfer */
+	int desc_cmpl;			/* completed descriptors */
+	int desc_cmpl_th;		/* completed descriptor threshold */
+	enum dma_data_direction dir;
+	struct swait_queue_head wq;	/* wait queue for transfer completion */
+
+	enum transfer_state state;	/* state of the transfer */
+	unsigned int flags;
+	int cyclic;			/* flag if transfer is cyclic */
+	int last_in_request;		/* flag if last within request */
+	unsigned int len;
+	struct sg_table *sgt;
+};
+
+struct xdma_request_cb {
+	struct sg_table *sgt;
+	unsigned int total_len;
+	u64 ep_addr;
+
+	struct xdma_transfer tfer;
+
+	unsigned int sw_desc_idx;
+	unsigned int sw_desc_cnt;
+	struct sw_desc sdesc[0];
+};
+
+struct xdma_engine {
+	struct xdma_dev *xdev;	/* parent device */
+	char name[16];		/* name of this engine */
+
+	/* HW register address offsets */
+	struct engine_regs *regs;		/* Control reg BAR offset */
+	struct engine_sgdma_regs *sgdma_regs;	/* SGDAM reg BAR offset */
+
+	/* Engine state, configuration and flags */
+	enum shutdown_state shutdown;	/* engine shutdown mode */
+	enum dma_data_direction dir;
+	u8 addr_align;		/* source/dest alignment in bytes */
+	u8 len_granularity;	/* transfer length multiple */
+	u8 addr_bits;		/* HW datapath address width */
+	u8 channel:2;		/* engine indices */
+	u8 streaming:1;
+	u8 device_open:1;	/* flag if engine node open, ST mode only */
+	u8 running:1;		/* flag if the driver started engine */
+	u8 non_incr_addr:1;	/* flag if non-incremental addressing used */
+	u8 eop_flush:1;		/* st c2h only, flush up the data with eop */
+	u8 filler:1;
+
+	int max_extra_adj;	/* descriptor prefetch capability */
+	int desc_dequeued;	/* num descriptors of completed transfers */
+	u32 status;		/* last known status of device */
+	u32 interrupt_enable_mask_value; /* per-engine interrupt mask value */
+
+	/* Transfer list management */
+	struct list_head transfer_list;	/* queue of transfers */
+
+	/* Members applicable to AXI-ST C2H (cyclic) transfers */
+	struct xdma_result *cyclic_result;
+	dma_addr_t cyclic_result_bus;	/* bus addr for transfer */
+
+	/* Members associated with interrupt mode support */
+	struct swait_queue_head shutdown_wq;
+	spinlock_t lock;		/* protects concurrent access */
+	int prev_cpu;			/* remember CPU# of (last) locker */
+	int irq_line;			/* IRQ vector for this engine */
+	u32 irq_bitmask;		/* IRQ bit mask for this engine */
+	struct work_struct work;	/* Work queue for interrupt handling */
+
+	struct mutex desc_lock;		/* protects concurrent access */
+	dma_addr_t desc_bus;
+	struct xdma_desc *desc;
+	int desc_idx;			/* current descriptor index */
+	int desc_used;			/* total descriptors used */
+};
+
+struct xdma_dev {
+	struct pci_dev *pdev;
+	void __iomem *config_bar;
+	unsigned int mask_irq_user;
+	int engines_num;
+	struct xdma_engine engine_h2c[XDMA_CHANNEL_NUM_MAX];
+	struct xdma_engine engine_c2h[XDMA_CHANNEL_NUM_MAX];
+};
+
+
+static void channel_interrupts_enable(struct xdma_dev *xdev, u32 mask)
+{
+	struct interrupt_regs *reg =
+		(struct interrupt_regs *)(xdev->config_bar + XDMA_OFS_INT_CTRL);
+
+	iowrite32(mask, &reg->channel_int_enable_w1s);
+}
+
+static void channel_interrupts_disable(struct xdma_dev *xdev, u32 mask)
+{
+	struct interrupt_regs *reg =
+		(struct interrupt_regs *)(xdev->config_bar + XDMA_OFS_INT_CTRL);
+
+	iowrite32(mask, &reg->channel_int_enable_w1c);
+}
+
+static void user_interrupts_enable(struct xdma_dev *xdev, u32 mask)
+{
+	struct interrupt_regs *reg =
+		(struct interrupt_regs *)(xdev->config_bar + XDMA_OFS_INT_CTRL);
+
+	iowrite32(mask, &reg->user_int_enable_w1s);
+}
+
+static void user_interrupts_disable(struct xdma_dev *xdev, u32 mask)
+{
+	struct interrupt_regs *reg =
+		(struct interrupt_regs *)(xdev->config_bar + XDMA_OFS_INT_CTRL);
+
+	iowrite32(mask, &reg->user_int_enable_w1c);
+}
+
+static void read_interrupts(struct xdma_dev *xdev)
+{
+	struct interrupt_regs *reg =
+		(struct interrupt_regs *)(xdev->config_bar + XDMA_OFS_INT_CTRL);
+	u32 lo, hi;
+
+	hi = ioread32(&reg->user_int_request);
+	lo = ioread32(&reg->channel_int_request);
+}
+
+static void engine_reg_dump(struct xdma_engine *engine)
+{
+	u32 w;
+
+	w = ioread32(&engine->regs->identifier);
+	if ((w & BLOCK_ID_MASK) != BLOCK_ID_HEAD) {
+		pr_warn("XDMA: %s: 0x%08x: invalid engine id\n",
+		       engine->name, w);
+		return;
+	}
+
+	pr_info("XDMA: %s: ENGINE REGISTER DUMP\n", engine->name);
+	pr_info("%s: ioread32(0x%p) = 0x%08x (id).\n",
+		engine->name, &engine->regs->identifier, w);
+	w = ioread32(&engine->regs->status);
+	pr_info("%s: ioread32(0x%p) = 0x%08x (status).\n",
+		engine->name, &engine->regs->status, w);
+	w = ioread32(&engine->regs->control);
+	pr_info("%s: ioread32(0x%p) = 0x%08x (control)\n",
+		engine->name, &engine->regs->control, w);
+	w = ioread32(&engine->sgdma_regs->first_desc_lo);
+	pr_info("%s: ioread32(0x%p) = 0x%08x (first_desc_lo)\n",
+		engine->name, &engine->sgdma_regs->first_desc_lo, w);
+	w = ioread32(&engine->sgdma_regs->first_desc_hi);
+	pr_info("%s: ioread32(0x%p) = 0x%08x (first_desc_hi)\n",
+		engine->name, &engine->sgdma_regs->first_desc_hi, w);
+	w = ioread32(&engine->sgdma_regs->first_desc_adjacent);
+	pr_info("%s: ioread32(0x%p) = 0x%08x (first_desc_adjacent).\n",
+		engine->name, &engine->sgdma_regs->first_desc_adjacent, w);
+	w = ioread32(&engine->regs->completed_desc_count);
+	pr_info("%s: ioread32(0x%p) = 0x%08x (completed_desc_count).\n",
+		engine->name, &engine->regs->completed_desc_count, w);
+	w = ioread32(&engine->regs->interrupt_enable_mask);
+	pr_info("%s: ioread32(0x%p) = 0x%08x (interrupt_enable_mask)\n",
+		engine->name, &engine->regs->interrupt_enable_mask, w);
+}
+
+static void engine_status_dump(struct xdma_engine *engine)
+{
+	u32 v = engine->status;
+	char buffer[256];
+	char *buf = buffer;
+	int len = 0;
+
+	len = sprintf(buf, "XDMA: %s: status: 0x%08x: ", engine->name, v);
+
+	if ((v & XDMA_STAT_BUSY))
+		len += sprintf(buf + len, "BUSY,");
+	if ((v & XDMA_STAT_DESC_STOPPED))
+		len += sprintf(buf + len, "DESC_STOPPED,");
+	if ((v & XDMA_STAT_DESC_COMPLETED))
+		len += sprintf(buf + len, "DESC_COMPL,");
+
+	/* common H2C & C2H */
+	if ((v & XDMA_STAT_COMMON_ERR_MASK)) {
+		if ((v & XDMA_STAT_ALIGN_MISMATCH))
+			len += sprintf(buf + len, "ALIGN_MISMATCH ");
+		if ((v & XDMA_STAT_MAGIC_STOPPED))
+			len += sprintf(buf + len, "MAGIC_STOPPED ");
+		if ((v & XDMA_STAT_INVALID_LEN))
+			len += sprintf(buf + len, "INVLIAD_LEN ");
+		if ((v & XDMA_STAT_IDLE_STOPPED))
+			len += sprintf(buf + len, "IDLE_STOPPED ");
+		buf[len - 1] = ',';
+	}
+
+	if (engine->dir == DMA_TO_DEVICE) {
+		/* H2C only */
+		if ((v & XDMA_STAT_H2C_R_ERR_MASK)) {
+			len += sprintf(buf + len, "R:");
+			if ((v & XDMA_STAT_H2C_R_UNSUPP_REQ))
+				len += sprintf(buf + len, "UNSUPP_REQ ");
+			if ((v & XDMA_STAT_H2C_R_COMPL_ABORT))
+				len += sprintf(buf + len, "COMPL_ABORT ");
+			if ((v & XDMA_STAT_H2C_R_PARITY_ERR))
+				len += sprintf(buf + len, "PARITY ");
+			if ((v & XDMA_STAT_H2C_R_HEADER_EP))
+				len += sprintf(buf + len, "HEADER_EP ");
+			if ((v & XDMA_STAT_H2C_R_UNEXP_COMPL))
+				len += sprintf(buf + len, "UNEXP_COMPL ");
+			buf[len - 1] = ',';
+		}
+
+		if ((v & XDMA_STAT_H2C_W_ERR_MASK)) {
+			len += sprintf(buf + len, "W:");
+			if ((v & XDMA_STAT_H2C_W_DECODE_ERR))
+				len += sprintf(buf + len, "DECODE_ERR ");
+			if ((v & XDMA_STAT_H2C_W_SLAVE_ERR))
+				len += sprintf(buf + len, "SLAVE_ERR ");
+			buf[len - 1] = ',';
+		}
+
+	} else {
+		/* C2H only */
+		if ((v & XDMA_STAT_C2H_R_ERR_MASK)) {
+			len += sprintf(buf + len, "R:");
+			if ((v & XDMA_STAT_C2H_R_DECODE_ERR))
+				len += sprintf(buf + len, "DECODE_ERR ");
+			if ((v & XDMA_STAT_C2H_R_SLAVE_ERR))
+				len += sprintf(buf + len, "SLAVE_ERR ");
+			buf[len - 1] = ',';
+		}
+	}
+
+	/* common H2C & C2H */
+	if ((v & XDMA_STAT_DESC_ERR_MASK)) {
+		len += sprintf(buf + len, "DESC_ERR:");
+		if ((v & XDMA_STAT_DESC_UNSUPP_REQ))
+			len += sprintf(buf + len, "UNSUPP_REQ ");
+		if ((v & XDMA_STAT_DESC_COMPL_ABORT))
+			len += sprintf(buf + len, "COMPL_ABORT ");
+		if ((v & XDMA_STAT_DESC_PARITY_ERR))
+			len += sprintf(buf + len, "PARITY ");
+		if ((v & XDMA_STAT_DESC_HEADER_EP))
+			len += sprintf(buf + len, "HEADER_EP ");
+		if ((v & XDMA_STAT_DESC_UNEXP_COMPL))
+			len += sprintf(buf + len, "UNEXP_COMPL ");
+		buf[len - 1] = ',';
+	}
+
+	buf[len - 1] = '\0';
+	pr_info("%s\n", buffer);
+}
+
+static void engine_status_read(struct xdma_engine *engine, bool clear, bool dump)
+{
+	if (dump)
+		engine_reg_dump(engine);
+
+	if (clear)
+		engine->status = ioread32(&engine->regs->status_rc);
+	else
+		engine->status = ioread32(&engine->regs->status);
+
+	if (dump)
+		engine_status_dump(engine);
+}
+
+static void engine_stop(struct xdma_engine *engine)
+{
+	u32 w;
+
+	if (enable_credit_mp && engine->streaming &&
+	    engine->dir == DMA_FROM_DEVICE)
+		iowrite32(0, &engine->sgdma_regs->credits);
+
+	w = 0;
+	w |= (u32)XDMA_CTRL_IE_DESC_ALIGN_MISMATCH;
+	w |= (u32)XDMA_CTRL_IE_MAGIC_STOPPED;
+	w |= (u32)XDMA_CTRL_IE_READ_ERROR;
+	w |= (u32)XDMA_CTRL_IE_DESC_ERROR;
+
+	w |= (u32)XDMA_CTRL_IE_DESC_STOPPED;
+	w |= (u32)XDMA_CTRL_IE_DESC_COMPLETED;
+
+	iowrite32(w, &engine->regs->control);
+
+	engine->running = 0;
+}
+
+static int engine_start_mode_config(struct xdma_engine *engine)
+{
+	u32 w;
+
+	/* write control register of SG DMA engine */
+	w = (u32)XDMA_CTRL_RUN_STOP;
+	w |= (u32)XDMA_CTRL_IE_READ_ERROR;
+	w |= (u32)XDMA_CTRL_IE_DESC_ERROR;
+	w |= (u32)XDMA_CTRL_IE_DESC_ALIGN_MISMATCH;
+	w |= (u32)XDMA_CTRL_IE_MAGIC_STOPPED;
+
+	w |= (u32)XDMA_CTRL_IE_DESC_STOPPED;
+	w |= (u32)XDMA_CTRL_IE_DESC_COMPLETED;
+
+	/* set non-incremental addressing mode */
+	if (engine->non_incr_addr)
+		w |= (u32)XDMA_CTRL_NON_INCR_ADDR;
+
+	/* start the engine */
+	iowrite32(w, &engine->regs->control);
+	/* dummy read of status register to flush all previous writes */
+	w = ioread32(&engine->regs->status);
+
+	return 0;
+}
+
+/*
+ * Get the number for adjacent descriptors to set in a descriptor, based on the
+ * remaining number of descriptors and the lower bits of the address of the
+ * next descriptor.
+ * Since the number of descriptors in a page (XDMA_PAGE_SIZE) is 128 and the
+ * maximum size of a block of adjacent descriptors is 64 (63 max adjacent
+ * descriptors for any descriptor), align the blocks of adjacent descriptors
+ * to the block size.
+ */
+static u32 xdma_get_next_adj(unsigned int remaining, u32 next_lo)
+{
+	unsigned int next_index;
+
+	if (remaining <= 1)
+		return 0;
+
+	/* shift right 5 times corresponds to a division by
+	 * sizeof(xdma_desc) = 32
+	 */
+	next_index = ((next_lo & (XDMA_PAGE_SIZE - 1)) >> 5) %
+		XDMA_MAX_ADJ_BLOCK_SIZE;
+	return min(XDMA_MAX_ADJ_BLOCK_SIZE - next_index - 1, remaining - 1);
+}
+
+/*
+ * start an idle engine with its first transfer on queue
+ *
+ * The engine will run and process all transfers that are queued using
+ * transfer_queue() and thus have their descriptor lists chained.
+ *
+ * During the run, new transfers will be processed if transfer_queue() has
+ * chained the descriptors before the hardware fetches the last descriptor.
+ * A transfer that was chained too late will invoke a new run of the engine
+ * initiated from the engine_service() routine.
+ *
+ * The engine must be idle and at least one transfer must be queued.
+ */
+static int engine_start(struct xdma_engine *engine)
+{
+	struct xdma_transfer *transfer;
+	u32 w, next_adj;
+	int rv;
+
+	/* engine transfer queue must not be empty */
+	if (list_empty(&engine->transfer_list)) {
+		pr_warn("XDMA: %s: transfer queue must not be empty\n",
+			engine->name);
+		return -EIO;
+	}
+	/* inspect first transfer queued on the engine */
+	transfer = list_entry(engine->transfer_list.next, struct xdma_transfer,
+			      entry);
+	if (!transfer) {
+		pr_warn("XDMA: %s: queued transfer must not be empty\n",
+			engine->name);
+		return -EIO;
+	}
+
+	/* engine is no longer shutdown */
+	engine->shutdown = ENGINE_SHUTDOWN_NONE;
+
+	/* Add credits for Streaming mode C2H */
+	if (enable_credit_mp && engine->streaming &&
+	    engine->dir == DMA_FROM_DEVICE)
+		iowrite32(engine->desc_used, &engine->sgdma_regs->credits);
+
+	/* initialize number of descriptors of dequeued transfers */
+	engine->desc_dequeued = 0;
+
+	/* write lower 32-bit of bus address of transfer first descriptor */
+	w = cpu_to_le32(PCI_DMA_L(transfer->desc_bus));
+	iowrite32(w, &engine->sgdma_regs->first_desc_lo);
+	/* write upper 32-bit of bus address of transfer first descriptor */
+	w = cpu_to_le32(PCI_DMA_H(transfer->desc_bus));
+	iowrite32(w, &engine->sgdma_regs->first_desc_hi);
+
+	next_adj = xdma_get_next_adj(transfer->desc_adjacent,
+				     cpu_to_le32(PCI_DMA_L(transfer->desc_bus)));
+	iowrite32(next_adj, &engine->sgdma_regs->first_desc_adjacent);
+
+	rv = engine_start_mode_config(engine);
+	if (rv < 0)
+		return rv;
+	engine_status_read(engine, 0, 0);
+
+	engine->running = 1;
+
+	return 0;
+}
+
+static void engine_service_shutdown(struct xdma_engine *engine)
+{
+	engine_stop(engine);
+	/* awake task on engine's shutdown wait queue */
+	swake_up_one(&engine->shutdown_wq);
+}
+
+static struct xdma_transfer *engine_transfer_completion(
+		struct xdma_engine *engine,
+		struct xdma_transfer *transfer)
+{
+	if (unlikely(!transfer)) {
+		pr_warn("XDMA: %s empty xfer\n", engine->name);
+		return NULL;
+	}
+
+	/* synchronous I/O? */
+	/* awake task on transfer's wait queue */
+	swake_up_one(&transfer->wq);
+
+	return transfer;
+}
+
+static struct xdma_transfer *engine_service_transfer_list(
+		struct xdma_engine *engine,
+		struct xdma_transfer *transfer,
+		u32 *pdesc_completed)
+{
+	if (unlikely(!transfer)) {
+		pr_warn("XDMA: %s empty xfer\n", engine->name);
+		return NULL;
+	}
+
+	/*
+	 * iterate over all the transfers completed by the engine,
+	 * except for the last
+	 */
+	while (transfer && (!transfer->cyclic) &&
+	       (*pdesc_completed > transfer->desc_num)) {
+		/* remove this transfer from pdesc_completed */
+		*pdesc_completed -= transfer->desc_num;
+
+		/* remove completed transfer from list */
+		list_del(engine->transfer_list.next);
+		/* add to dequeued number of descriptors during this run */
+		engine->desc_dequeued += transfer->desc_num;
+		/* mark transfer as successfully completed */
+		transfer->state = TRANSFER_STATE_COMPLETED;
+
+		/*
+		 * Complete transfer - sets transfer to NULL if an async
+		 * transfer has completed
+		 */
+		transfer = engine_transfer_completion(engine, transfer);
+
+		/* if exists, get the next transfer on the list */
+		if (!list_empty(&engine->transfer_list)) {
+			transfer = list_entry(engine->transfer_list.next,
+					      struct xdma_transfer, entry);
+		} else {
+			/* no further transfers? */
+			transfer = NULL;
+		}
+	}
+
+	return transfer;
+}
+
+static void engine_err_handle(struct xdma_engine *engine,
+			      struct xdma_transfer *transfer)
+{
+	u32 value;
+
+	/*
+	 * The BUSY bit is expected to be clear now but older HW has a race
+	 * condition which could cause it to be still set.  If it's set, re-read
+	 * and check again.  If it's still set, log the issue.
+	 */
+	if (engine->status & XDMA_STAT_BUSY) {
+		value = ioread32(&engine->regs->status);
+		if ((value & XDMA_STAT_BUSY))
+			pr_warn("XDMA: %s has errors but is still BUSY\n",
+				engine->name);
+	}
+
+	/* mark transfer as failed */
+	transfer->state = TRANSFER_STATE_FAILED;
+	engine_stop(engine);
+}
+
+static struct xdma_transfer *
+engine_service_final_transfer(struct xdma_engine *engine,
+			      struct xdma_transfer *transfer,
+			      u32 *pdesc_completed)
+{
+	/* inspect the current transfer */
+	if (unlikely(!transfer)) {
+		pr_warn("XDMA: %s: empty xfer\n", engine->name);
+		return NULL;
+	}
+
+	if (((engine->dir == DMA_FROM_DEVICE) &&
+	     (engine->status & XDMA_STAT_C2H_ERR_MASK)) ||
+	    ((engine->dir == DMA_TO_DEVICE) &&
+	     (engine->status & XDMA_STAT_H2C_ERR_MASK))) {
+		pr_warn("XDMA: %s: status error 0x%x.\n", engine->name,
+			engine->status);
+		engine_status_dump(engine);
+		engine_err_handle(engine, transfer);
+		goto transfer_del;
+	}
+
+	if (engine->status & XDMA_STAT_BUSY)
+		pr_info("XDMA: %s: engine unexpectedly busy, ignoring\n",
+			engine->name);
+
+	/* the engine stopped on current transfer? */
+	if (*pdesc_completed < transfer->desc_num) {
+		if (engine->eop_flush) {
+			/* check if eop received */
+			struct xdma_result *result = transfer->res_virt;
+			int i;
+			int max = *pdesc_completed;
+
+			for (i = 0; i < max; i++) {
+				if ((result[i].status & RX_STATUS_EOP) != 0) {
+					transfer->flags |=
+						XFER_FLAG_ST_C2H_EOP_RCVED;
+					break;
+				}
+			}
+
+			transfer->desc_cmpl += *pdesc_completed;
+			if (!(transfer->flags & XFER_FLAG_ST_C2H_EOP_RCVED))
+				return NULL;
+
+			/* mark transfer as successfully completed */
+			engine_service_shutdown(engine);
+			transfer->state = TRANSFER_STATE_COMPLETED;
+			engine->desc_dequeued += transfer->desc_cmpl;
+		} else {
+			transfer->state = TRANSFER_STATE_FAILED;
+			pr_warn("XDMA: %s: xfer stopped half-way\n",
+				engine->name);
+
+			/* add dequeued number of descriptors during this run */
+			engine->desc_dequeued += transfer->desc_num;
+			transfer->desc_cmpl = *pdesc_completed;
+		}
+	} else {
+		if (!transfer->cyclic) {
+			/*
+			 * if the engine stopped on this transfer,
+			 * it should be the last
+			 */
+			WARN_ON(*pdesc_completed > transfer->desc_num);
+		}
+		/* mark transfer as successfully completed */
+		transfer->state = TRANSFER_STATE_COMPLETED;
+		transfer->desc_cmpl = transfer->desc_num;
+		/* add dequeued number of descriptors during this run */
+		engine->desc_dequeued += transfer->desc_num;
+	}
+
+transfer_del:
+	/* remove completed transfer from list */
+	list_del(engine->transfer_list.next);
+
+	/*
+	 * Complete transfer - sets transfer to NULL if an asynchronous
+	 * transfer has completed
+	 */
+	transfer = engine_transfer_completion(engine, transfer);
+
+	return transfer;
+}
+
+static int engine_service_resume(struct xdma_engine *engine)
+{
+	int rv;
+
+	if (!engine->running) {
+		/* in the case of shutdown, let it finish what's in the Q */
+		if (!list_empty(&engine->transfer_list)) {
+			/* (re)start engine */
+			rv = engine_start(engine);
+			if (rv)
+				return rv;
+			/* engine was requested to be shutdown? */
+		} else if (engine->shutdown & ENGINE_SHUTDOWN_REQUEST) {
+			engine->shutdown |= ENGINE_SHUTDOWN_IDLE;
+			/* awake task on engine's shutdown wait queue */
+			swake_up_one(&engine->shutdown_wq);
+		}
+	} else if (list_empty(&engine->transfer_list)) {
+		engine_service_shutdown(engine);
+	}
+
+	return 0;
+}
+
+static int engine_service(struct xdma_engine *engine, int desc_writeback)
+{
+	struct xdma_transfer *transfer = NULL;
+	u32 desc_count = desc_writeback & WB_COUNT_MASK;
+	u32 err_flag = desc_writeback & WB_ERR_MASK;
+	int rv;
+
+	if (!engine->running) {
+		engine_status_read(engine, 1, 0);
+		return 0;
+	}
+
+	/*
+	 * If called by the ISR detected an error, read and clear
+	 * engine status.
+	 */
+	if ((desc_count == 0) || (err_flag != 0))
+		engine_status_read(engine, 1, 0);
+
+	/*
+	 * engine was running but is no longer busy, or writeback occurred,
+	 * shut down
+	 */
+	if ((engine->running && !(engine->status & XDMA_STAT_BUSY)) ||
+	    (!engine->eop_flush && desc_count != 0))
+		engine_service_shutdown(engine);
+
+	/*
+	 * If called from the ISR, or if an error occurred, the descriptor
+	 * count will be zero.  In this scenario, read the descriptor count
+	 * from HW.
+	 */
+	if (!desc_count)
+		desc_count = ioread32(&engine->regs->completed_desc_count);
+	if (!desc_count)
+		goto done;
+
+	/* transfers on queue? */
+	if (!list_empty(&engine->transfer_list)) {
+		/* pick first transfer on queue (was submitted to the engine) */
+		transfer = list_entry(engine->transfer_list.next,
+				      struct xdma_transfer, entry);
+	}
+
+	/* account for already dequeued transfers during this engine run */
+	desc_count -= engine->desc_dequeued;
+
+	/* Process all but the last transfer */
+	transfer = engine_service_transfer_list(engine, transfer, &desc_count);
+
+	/*
+	 * Process final transfer - includes checks of number of descriptors to
+	 * detect faulty completion
+	 */
+	transfer = engine_service_final_transfer(engine, transfer, &desc_count);
+
+	/* Restart the engine following the servicing */
+	if (!engine->eop_flush) {
+		rv = engine_service_resume(engine);
+		if (rv)
+			return rv;
+	}
+
+done:
+	return err_flag ? -1 : 0;
+}
+
+static void engine_service_work(struct work_struct *work)
+{
+	struct xdma_engine *engine;
+	unsigned long flags;
+	int rv;
+
+	engine = container_of(work, struct xdma_engine, work);
+
+	spin_lock_irqsave(&engine->lock, flags);
+
+	rv = engine_service(engine, 0);
+	if (rv < 0)
+		goto unlock;
+
+	/* re-enable interrupts for this engine */
+	iowrite32(engine->interrupt_enable_mask_value,
+		  &engine->regs->interrupt_enable_mask_w1s);
+
+unlock:
+	spin_unlock_irqrestore(&engine->lock, flags);
+}
+
+static irqreturn_t xdma_isr(int irq, void *dev_id)
+{
+	struct xdma_dev *xdev;
+	struct xdma_engine *engine;
+	struct interrupt_regs *irq_regs;
+
+	engine = (struct xdma_engine *)dev_id;
+	xdev = engine->xdev;
+
+	irq_regs = (struct interrupt_regs *)(xdev->config_bar + XDMA_OFS_INT_CTRL);
+
+	/* Disable the interrupt for this engine */
+	iowrite32(engine->interrupt_enable_mask_value,
+		&engine->regs->interrupt_enable_mask_w1c);
+	/* Dummy read to flush the above write */
+	ioread32(&irq_regs->channel_int_pending);
+	schedule_work(&engine->work);
+
+	return IRQ_HANDLED;
+}
+
+static int is_config_bar(void *bar)
+{
+	u32 irq_id = 0;
+	u32 cfg_id = 0;
+	u32 mask = 0xffff0000; /* Compare only XDMA ID's not Version number */
+	struct interrupt_regs *irq_regs =
+		(struct interrupt_regs *)(bar + XDMA_OFS_INT_CTRL);
+	struct config_regs *cfg_regs =
+		(struct config_regs *)(bar + XDMA_OFS_CONFIG);
+
+	irq_id = ioread32(&irq_regs->identifier);
+	cfg_id = ioread32(&cfg_regs->identifier);
+
+	if (((irq_id & mask) == IRQ_BLOCK_ID)
+	    && ((cfg_id & mask) == CONFIG_BLOCK_ID))
+		return 1;
+
+	return 0;
+}
+
+static void unmap_config_bar(struct xdma_dev *xdev, int config_bar_id)
+{
+	pci_iounmap(xdev->pdev, xdev->config_bar);
+	pci_release_selected_regions(xdev->pdev, 1U<<config_bar_id);
+}
+
+static int map_config_bar(struct xdma_dev *xdev, int config_bar_id)
+{
+	int rv, bar_len;
+
+	bar_len = pci_resource_len(xdev->pdev, config_bar_id);
+	if (bar_len < XDMA_BAR_SIZE) {
+		pr_err("XDMA: %d: Not a config BAR\n", config_bar_id);
+		return -EINVAL;
+	}
+	rv = pci_request_selected_regions(xdev->pdev, 1U<<config_bar_id,
+					  "xdma");
+	if (rv) {
+		pr_err("XDMA: Failed to request config BAR memory\n");
+		return rv;
+	}
+	xdev->config_bar = pci_iomap(xdev->pdev, config_bar_id, bar_len);
+	if (!xdev->config_bar) {
+		pr_err("XDMA: Failed to map config BAR memory\n");
+		rv = -ENOMEM;
+		goto err_map;
+	}
+	if (!is_config_bar(xdev->config_bar)) {
+		pr_err("XDMA: %d: Not a config BAR\n", config_bar_id);
+		rv = -EINVAL;
+		goto err_bar;
+	}
+
+	pr_debug("XDMA: Config BAR %d mapped at %p\n",
+		 config_bar_id, xdev->config_bar);
+
+	return 0;
+
+err_bar:
+	pci_iounmap(xdev->pdev, xdev->config_bar);
+err_map:
+	pci_release_selected_regions(xdev->pdev, 1U<<config_bar_id);
+
+	return rv;
+}
+
+static void prog_irq_user(struct xdma_dev *xdev, int num_channel, int num_irq,
+			  bool clear)
+{
+	struct interrupt_regs *int_regs =
+		(struct interrupt_regs *)(xdev->config_bar + XDMA_OFS_INT_CTRL);
+	int i = num_channel;
+	int max = i + num_irq;
+	int j;
+
+	for (j = 0; i < max; j++) {
+		u32 val = 0;
+		int k, shift = 0;
+
+		if (clear)
+			i += 4;
+		else
+			for (k = 0; k < 4 && i < max; i++, k++, shift += 8)
+				val |= (i & 0x1f) << shift;
+
+		iowrite32(val, &int_regs->user_msi_vector[j]);
+	}
+}
+
+static void prog_irq_channel(struct xdma_dev *xdev, int num_channel, bool clear)
+{
+	struct interrupt_regs *int_regs =
+		(struct interrupt_regs *)(xdev->config_bar + XDMA_OFS_INT_CTRL);
+	int i, j;
+
+	for (i = 0, j = 0; i < num_channel; j++) {
+		u32 val = 0;
+		int k, shift = 0;
+
+		if (clear)
+			i += 4;
+		else
+			for (k = 0; k < 4 && i < num_channel; i++, k++, shift += 8)
+				val |= (i & 0x1f) << shift;
+
+		iowrite32(val, &int_regs->channel_msi_vector[j]);
+	}
+}
+
+static void irq_channel_teardown(struct xdma_dev *xdev, int h2c_channel_max,
+				 int c2h_channel_max)
+{
+	struct xdma_engine *engine;
+	int i = 0, j = 0;
+
+	engine = xdev->engine_h2c;
+	for (i = 0; i < h2c_channel_max; i++, j++, engine++) {
+		if (!engine->irq_line)
+			break;
+		free_irq(engine->irq_line, engine);
+	}
+
+	engine = xdev->engine_c2h;
+	for (i = 0; i < c2h_channel_max; i++, j++, engine++) {
+		if (!engine->irq_line)
+			break;
+		free_irq(engine->irq_line, engine);
+	}
+}
+
+static int irq_channel_setup(struct xdma_dev *xdev, int h2c_channel_max,
+			     int c2h_channel_max)
+{
+	int i, j, rv;
+	u32 vector;
+	struct xdma_engine *engine;
+
+	j = h2c_channel_max;
+	engine = xdev->engine_h2c;
+	for (i = 0; i < h2c_channel_max; i++, engine++) {
+		vector = pci_irq_vector(xdev->pdev, i);
+		rv = request_irq(vector, xdma_isr, 0, engine->name, engine);
+		if (rv) {
+			pr_err("XDMA: %s: error requesting irq#%d\n",
+			       engine->name, vector);
+			return rv;
+		}
+		pr_info("XDMA: %s: irq#%d\n", engine->name, vector);
+		engine->irq_line = vector;
+	}
+
+	engine = xdev->engine_c2h;
+	for (i = 0; i < c2h_channel_max; i++, j++, engine++) {
+		vector = pci_irq_vector(xdev->pdev, j);
+		rv = request_irq(vector, xdma_isr, 0, engine->name, engine);
+		if (rv) {
+			pr_err("XDMA: %s: error requesting irq#%d\n",
+			       engine->name, vector);
+			return rv;
+		}
+		pr_info("XDMA: %s: irq#%d\n", engine->name, vector);
+		engine->irq_line = vector;
+	}
+
+	return 0;
+}
+
+static void irq_teardown(struct xdma_dev *xdev, int h2c_channel_max,
+			 int c2h_channel_max, int user_irq_max)
+{
+	int num_channel = h2c_channel_max + c2h_channel_max;
+
+	prog_irq_user(xdev, num_channel, user_irq_max, 1);
+	prog_irq_channel(xdev, num_channel, 1);
+
+	irq_channel_teardown(xdev, h2c_channel_max, c2h_channel_max);
+}
+
+static int irq_setup(struct xdma_dev *xdev, int h2c_channel_max,
+		     int c2h_channel_max, int user_irq_max)
+{
+	int rv;
+	int num_channel = h2c_channel_max + c2h_channel_max;
+
+	rv = irq_channel_setup(xdev, h2c_channel_max, c2h_channel_max);
+	if (rv)
+		return rv;
+
+	prog_irq_channel(xdev, num_channel, 0);
+	prog_irq_user(xdev, num_channel, user_irq_max, 0);
+
+	return 0;
+}
+
+/* Chains the descriptors as a singly-linked list
+ *
+ * Each descriptor's next pointer specifies the bus address of the next
+ * descriptor.
+ * Terminates the last descriptor to form a singly-linked list.
+ */
+static void transfer_desc_init(struct xdma_transfer *transfer, int count)
+{
+	struct xdma_desc *desc_virt = transfer->desc_virt;
+	dma_addr_t desc_bus = transfer->desc_bus;
+	int i;
+
+	BUG_ON(count > XDMA_TRANSFER_MAX_DESC);
+
+	/* create singly-linked list for SG DMA controller */
+	for (i = 0; i < count - 1; i++) {
+		/* increment bus address to next in array */
+		desc_bus += sizeof(struct xdma_desc);
+
+		/* singly-linked list uses bus addresses */
+		desc_virt[i].next_lo = cpu_to_le32(PCI_DMA_L(desc_bus));
+		desc_virt[i].next_hi = cpu_to_le32(PCI_DMA_H(desc_bus));
+		desc_virt[i].bytes = cpu_to_le32(0);
+
+		desc_virt[i].control = cpu_to_le32(DESC_MAGIC);
+	}
+
+	/* zero the last descriptor next pointer */
+	desc_virt[i].next_lo = cpu_to_le32(0);
+	desc_virt[i].next_hi = cpu_to_le32(0);
+	desc_virt[i].bytes = cpu_to_le32(0);
+	desc_virt[i].control = cpu_to_le32(DESC_MAGIC);
+}
+
+/* Set how many descriptors are adjacent to this one */
+static void xdma_desc_adjacent(struct xdma_desc *desc, u32 next_adjacent)
+{
+	/* remember reserved and control bits */
+	u32 control = le32_to_cpu(desc->control) & 0x0000f0ffUL;
+	/* merge adjacent and control field */
+	control |= 0xAD4B0000UL | (next_adjacent << 8);
+	/* write control and next_adjacent */
+	desc->control = cpu_to_le32(control);
+}
+
+/* Set complete control field of a descriptor */
+static void xdma_desc_control_set(struct xdma_desc *first, u32 control_field)
+{
+	/* remember magic and adjacent number */
+	u32 control = le32_to_cpu(first->control) & ~(LS_BYTE_MASK);
+
+	/* merge adjacent and control field */
+	control |= control_field;
+	/* write control and next_adjacent */
+	first->control = cpu_to_le32(control);
+}
+
+static inline void xdma_desc_done(struct xdma_desc *desc_virt, int count)
+{
+	memset(desc_virt, 0, count * sizeof(struct xdma_desc));
+}
+
+/* Fill a descriptor with the transfer details */
+static void xdma_desc_set(struct xdma_desc *desc, dma_addr_t rc_bus_addr,
+			  u64 ep_addr, int len, int dir)
+{
+	/* transfer length */
+	desc->bytes = cpu_to_le32(len);
+	if (dir == DMA_TO_DEVICE) {
+		/* read from root complex memory (source address) */
+		desc->src_addr_lo = cpu_to_le32(PCI_DMA_L(rc_bus_addr));
+		desc->src_addr_hi = cpu_to_le32(PCI_DMA_H(rc_bus_addr));
+		/* write to end point address (destination address) */
+		desc->dst_addr_lo = cpu_to_le32(PCI_DMA_L(ep_addr));
+		desc->dst_addr_hi = cpu_to_le32(PCI_DMA_H(ep_addr));
+	} else {
+		/* read from end point address (source address) */
+		desc->src_addr_lo = cpu_to_le32(PCI_DMA_L(ep_addr));
+		desc->src_addr_hi = cpu_to_le32(PCI_DMA_H(ep_addr));
+		/* write to root complex memory (destination address) */
+		desc->dst_addr_lo = cpu_to_le32(PCI_DMA_L(rc_bus_addr));
+		desc->dst_addr_hi = cpu_to_le32(PCI_DMA_H(rc_bus_addr));
+	}
+}
+
+static void transfer_abort(struct xdma_engine *engine,
+			  struct xdma_transfer *transfer)
+{
+	struct xdma_transfer *head;
+
+	head = list_entry(engine->transfer_list.next, struct xdma_transfer,
+			  entry);
+	if (head == transfer)
+		list_del(engine->transfer_list.next);
+	else
+		pr_warn("XDMA: %s: transfer for abort NOT found\n",
+			engine->name);
+
+	if (transfer->state == TRANSFER_STATE_SUBMITTED)
+		transfer->state = TRANSFER_STATE_ABORTED;
+}
+
+static int transfer_queue(struct xdma_engine *engine,
+			  struct xdma_transfer *transfer)
+{
+	int rv = 0;
+	unsigned long flags;
+
+	/* lock the engine state */
+	spin_lock_irqsave(&engine->lock, flags);
+
+	engine->prev_cpu = get_cpu();
+	put_cpu();
+
+	/* engine is being shutdown; do not accept new transfers */
+	if (engine->shutdown & ENGINE_SHUTDOWN_REQUEST) {
+		pr_info("XDMA: %s: engine offline, transfer not queued\n",
+			engine->name);
+		rv = -EBUSY;
+		goto shutdown;
+	}
+
+	/* mark the transfer as submitted */
+	transfer->state = TRANSFER_STATE_SUBMITTED;
+	/* add transfer to the tail of the engine transfer queue */
+	list_add_tail(&transfer->entry, &engine->transfer_list);
+
+	if (!engine->running)
+		rv = engine_start(engine);
+
+shutdown:
+	spin_unlock_irqrestore(&engine->lock, flags);
+
+	return rv;
+}
+
+static void engine_alignments(struct xdma_engine *engine)
+{
+	u32 w = ioread32(&engine->regs->alignments);
+
+	if (w) {
+		engine->addr_align = (w & 0x00ff0000U) >> 16;
+		engine->len_granularity = (w & 0x0000ff00U) >> 8;
+		engine->addr_bits = (w & 0x000000ffU);
+	} else {
+		/* Some default values if alignments are unspecified */
+		engine->addr_align = 1;
+		engine->len_granularity = 1;
+		engine->addr_bits = 64;
+	}
+}
+
+static void engine_free_resource(struct xdma_engine *engine)
+{
+	struct xdma_dev *xdev = engine->xdev;
+
+	if (engine->desc) {
+		dma_free_coherent(&xdev->pdev->dev,
+				  XDMA_TRANSFER_MAX_DESC *
+					  sizeof(struct xdma_desc),
+				  engine->desc, engine->desc_bus);
+		engine->desc = NULL;
+	}
+
+	if (engine->cyclic_result) {
+		dma_free_coherent(
+			&xdev->pdev->dev,
+			XDMA_TRANSFER_MAX_DESC * sizeof(struct xdma_result),
+			engine->cyclic_result, engine->cyclic_result_bus);
+		engine->cyclic_result = NULL;
+	}
+}
+
+static void engine_destroy(struct xdma_dev *xdev, struct xdma_engine *engine)
+{
+	/* Disable interrupts to stop processing new events during shutdown */
+	iowrite32(0x0, &engine->regs->interrupt_enable_mask);
+
+	if (enable_credit_mp && engine->streaming &&
+	    engine->dir == DMA_FROM_DEVICE) {
+		u32 reg_value = (0x1 << engine->channel) << 16;
+		struct sgdma_common_regs *reg =
+			(struct sgdma_common_regs *)
+			(xdev->config_bar + (0x6 * TARGET_SPACING));
+		iowrite32(reg_value, &reg->credit_mode_enable_w1c);
+	}
+
+	/* Release memory use for descriptor writebacks */
+	engine_free_resource(engine);
+
+	memset(engine, 0, sizeof(struct xdma_engine));
+	/* Decrement the number of engines available */
+	xdev->engines_num--;
+}
+
+static void engine_init_regs(struct xdma_engine *engine)
+{
+	u32 reg_value;
+
+	iowrite32(XDMA_CTRL_NON_INCR_ADDR, &engine->regs->control_w1c);
+
+	engine_alignments(engine);
+
+	/* Configure error interrupts by default */
+	reg_value = XDMA_CTRL_IE_DESC_ALIGN_MISMATCH;
+	reg_value |= XDMA_CTRL_IE_MAGIC_STOPPED;
+	reg_value |= XDMA_CTRL_IE_MAGIC_STOPPED;
+	reg_value |= XDMA_CTRL_IE_READ_ERROR;
+	reg_value |= XDMA_CTRL_IE_DESC_ERROR;
+
+	/* enable the relevant completion interrupts */
+	reg_value |= XDMA_CTRL_IE_DESC_STOPPED;
+	reg_value |= XDMA_CTRL_IE_DESC_COMPLETED;
+
+	/* Apply engine configurations */
+	iowrite32(reg_value, &engine->regs->interrupt_enable_mask);
+
+	engine->interrupt_enable_mask_value = reg_value;
+
+	/* only enable credit mode for AXI-ST C2H */
+	if (enable_credit_mp && engine->streaming &&
+	    engine->dir == DMA_FROM_DEVICE) {
+		struct xdma_dev *xdev = engine->xdev;
+		u32 reg_value = (0x1 << engine->channel) << 16;
+		struct sgdma_common_regs *reg =
+			(struct sgdma_common_regs *)
+			(xdev->config_bar + (0x6 * TARGET_SPACING));
+
+		iowrite32(reg_value, &reg->credit_mode_enable_w1s);
+	}
+}
+
+static int engine_alloc_resource(struct xdma_engine *engine)
+{
+	struct xdma_dev *xdev = engine->xdev;
+
+	engine->desc = dma_alloc_coherent(&xdev->pdev->dev,
+					  XDMA_TRANSFER_MAX_DESC *
+						  sizeof(struct xdma_desc),
+					  &engine->desc_bus, GFP_KERNEL);
+	if (!engine->desc)
+		goto err_out;
+
+	if (engine->streaming && engine->dir == DMA_FROM_DEVICE) {
+		engine->cyclic_result = dma_alloc_coherent(
+			&xdev->pdev->dev,
+			XDMA_TRANSFER_MAX_DESC * sizeof(struct xdma_result),
+			&engine->cyclic_result_bus, GFP_KERNEL);
+
+		if (!engine->cyclic_result)
+			goto err_out;
+	}
+
+	return 0;
+
+err_out:
+	engine_free_resource(engine);
+	return -ENOMEM;
+}
+
+static int engine_init(struct xdma_engine *engine, struct xdma_dev *xdev,
+		       int offset, enum dma_data_direction dir, int channel)
+{
+	int rv;
+	u32 val;
+
+	engine->channel = channel;
+	engine->xdev = xdev;
+
+	/* engine interrupt request bit */
+	engine->irq_bitmask = (1 << XDMA_ENG_IRQ_NUM) - 1;
+	engine->irq_bitmask <<= (xdev->engines_num * XDMA_ENG_IRQ_NUM);
+
+	/* register address */
+	engine->regs = xdev->config_bar + offset;
+	engine->sgdma_regs = xdev->config_bar + offset +
+			     SGDMA_OFFSET_FROM_CHANNEL;
+	val = ioread32(&engine->regs->identifier);
+	if (val & 0x8000U)
+		engine->streaming = 1;
+
+	/* remember SG DMA direction */
+	engine->dir = dir;
+	sprintf(engine->name, "xdma-%s%d%s", (dir == DMA_TO_DEVICE) ? "H2C" : "C2H",
+		channel, engine->streaming ? "ST" : "MM");
+
+	/* initialize the deferred work for transfer completion */
+	INIT_WORK(&engine->work, engine_service_work);
+
+	xdev->engines_num++;
+
+	rv = engine_alloc_resource(engine);
+	if (rv)
+		return rv;
+	engine_init_regs(engine);
+
+	return 0;
+}
+
+static void transfer_destroy(struct xdma_dev *xdev, struct xdma_transfer *xfer)
+{
+	xdma_desc_done(xfer->desc_virt, xfer->desc_num);
+
+	if (xfer->last_in_request && (xfer->flags & XFER_FLAG_NEED_UNMAP)) {
+		struct sg_table *sgt = xfer->sgt;
+
+		if (sgt->nents) {
+			dma_unmap_sg(&xdev->pdev->dev, sgt->sgl, sgt->nents,
+				     xfer->dir);
+			sgt->nents = 0;
+		}
+	}
+}
+
+static void transfer_build(struct xdma_engine *engine,
+			struct xdma_request_cb *req, struct xdma_transfer *xfer,
+			unsigned int desc_max)
+{
+	struct sw_desc *sdesc = &(req->sdesc[req->sw_desc_idx]);
+	int i, j;
+	dma_addr_t bus = xfer->res_bus;
+
+	for (i = 0, j = 0; i < desc_max; i++, j++, sdesc++) {
+		/* fill in descriptor entry j with transfer details */
+		xdma_desc_set(xfer->desc_virt + j, sdesc->addr, req->ep_addr,
+			      sdesc->len, xfer->dir);
+		xfer->len += sdesc->len;
+
+		/* for non-inc-add mode don't increment ep_addr */
+		if (!engine->non_incr_addr)
+			req->ep_addr += sdesc->len;
+
+		if (engine->streaming && engine->dir == DMA_FROM_DEVICE) {
+			memset(xfer->res_virt + j, 0,
+				sizeof(struct xdma_result));
+			xfer->desc_virt[j].src_addr_lo =
+						cpu_to_le32(PCI_DMA_L(bus));
+			xfer->desc_virt[j].src_addr_hi =
+						cpu_to_le32(PCI_DMA_H(bus));
+			bus += sizeof(struct xdma_result);
+		}
+
+	}
+
+	req->sw_desc_idx += desc_max;
+}
+
+static void transfer_init(struct xdma_engine *engine,
+			struct xdma_request_cb *req, struct xdma_transfer *xfer)
+{
+	unsigned int desc_max = min_t(unsigned int,
+				req->sw_desc_cnt - req->sw_desc_idx,
+				XDMA_TRANSFER_MAX_DESC);
+	int i, last;
+	u32 control;
+	unsigned long flags;
+
+	memset(xfer, 0, sizeof(*xfer));
+
+	spin_lock_irqsave(&engine->lock, flags);
+	init_swait_queue_head(&xfer->wq);
+
+	/* remember direction of transfer */
+	xfer->dir = engine->dir;
+	xfer->desc_virt = engine->desc + engine->desc_idx;
+	xfer->res_virt = engine->cyclic_result + engine->desc_idx;
+	xfer->desc_bus = engine->desc_bus +
+			(sizeof(struct xdma_desc) * engine->desc_idx);
+	xfer->res_bus = engine->cyclic_result_bus +
+			(sizeof(struct xdma_result) * engine->desc_idx);
+	xfer->desc_index = engine->desc_idx;
+
+	if ((engine->desc_idx + desc_max) >= XDMA_TRANSFER_MAX_DESC)
+		desc_max = XDMA_TRANSFER_MAX_DESC - engine->desc_idx;
+
+	transfer_desc_init(xfer, desc_max);
+	transfer_build(engine, req, xfer, desc_max);
+
+	xfer->desc_adjacent = desc_max;
+
+	/* terminate last descriptor */
+	last = desc_max - 1;
+	/* stop engine, EOP for AXI ST, req IRQ on last descriptor */
+	control = XDMA_DESC_STOPPED;
+	control |= XDMA_DESC_EOP;
+	control |= XDMA_DESC_COMPLETED;
+	xdma_desc_control_set(xfer->desc_virt + last, control);
+
+	if (engine->eop_flush) {
+		for (i = 0; i < last; i++)
+			xdma_desc_control_set(xfer->desc_virt + i,
+					XDMA_DESC_COMPLETED);
+		xfer->desc_cmpl_th = 1;
+	} else
+		xfer->desc_cmpl_th = desc_max;
+
+	xfer->desc_num = desc_max;
+	engine->desc_idx = (engine->desc_idx + desc_max) % XDMA_TRANSFER_MAX_DESC;
+	engine->desc_used += desc_max;
+
+	/* fill in adjacent numbers */
+	for (i = 0; i < xfer->desc_num; i++) {
+		u32 next_adj = xdma_get_next_adj(xfer->desc_num - i - 1,
+						(xfer->desc_virt + i)->next_lo);
+		xdma_desc_adjacent(xfer->desc_virt + i, next_adj);
+	}
+
+	spin_unlock_irqrestore(&engine->lock, flags);
+}
+
+static void xdma_request_free(struct xdma_request_cb *req)
+{
+	kvfree(req);
+}
+
+static struct xdma_request_cb *xdma_request_alloc(struct xdma_dev *xdev,
+						  unsigned int sdesc_nr)
+{
+	unsigned int size = sizeof(struct xdma_request_cb) +
+			    sdesc_nr * sizeof(struct sw_desc);
+
+	return kvzalloc(size, GFP_KERNEL);
+}
+
+static struct xdma_request_cb *xdma_init_request(struct xdma_dev *xdev,
+						 struct sg_table *sgt,
+						 u64 ep_addr)
+{
+	struct xdma_request_cb *req;
+	struct scatterlist *sg = sgt->sgl;
+	int max = sgt->nents;
+	int extra = 0;
+	int i, j = 0;
+
+	for (i = 0; i < max; i++, sg = sg_next(sg)) {
+		unsigned int len = sg_dma_len(sg);
+
+		if (unlikely(len > XDMA_DESC_BLEN_MAX))
+			extra += (len + XDMA_DESC_BLEN_MAX - 1) / XDMA_DESC_BLEN_MAX;
+	}
+
+	max += extra;
+	req = xdma_request_alloc(xdev, max);
+	if (!req)
+		return NULL;
+
+	req->sgt = sgt;
+	req->ep_addr = ep_addr;
+
+	for (i = 0, sg = sgt->sgl; i < sgt->nents; i++, sg = sg_next(sg)) {
+		unsigned int tlen = sg_dma_len(sg);
+		dma_addr_t addr = sg_dma_address(sg);
+
+		req->total_len += tlen;
+		while (tlen) {
+			req->sdesc[j].addr = addr;
+			if (tlen > XDMA_DESC_BLEN_MAX) {
+				req->sdesc[j].len = XDMA_DESC_BLEN_MAX;
+				addr += XDMA_DESC_BLEN_MAX;
+				tlen -= XDMA_DESC_BLEN_MAX;
+			} else {
+				req->sdesc[j].len = tlen;
+				tlen = 0;
+			}
+			j++;
+		}
+	}
+
+	if (j > max) {
+		pr_err("XDMA: Max. transfer length (%d) exceeded",
+		       XDMA_DESC_BLEN_MAX);
+		xdma_request_free(req);
+		return NULL;
+	}
+	req->sw_desc_cnt = j;
+
+	return req;
+}
+
+static struct xdma_engine *channel_engine(struct xdma_core *xdma, int channel,
+					  bool write)
+{
+	if (write) {
+		if (channel >= xdma->h2c_channel_max) {
+			pr_err("XDMA: %d: invalid H2C channel\n", channel);
+			return NULL;
+		} else
+			return &xdma->xdev->engine_h2c[channel];
+	} else {
+		if (channel >= xdma->c2h_channel_max) {
+			pr_err("XDMA: %d: invalid C2H channel\n", channel);
+			return NULL;
+		} else
+			return &xdma->xdev->engine_c2h[channel];
+	}
+}
+
+static struct xdma_dev *alloc_dev(struct pci_dev *pdev)
+{
+	int i;
+	struct xdma_dev *xdev;
+	struct xdma_engine *engine;
+
+	xdev = kzalloc(sizeof(struct xdma_dev), GFP_KERNEL);
+	if (!xdev)
+		return NULL;
+
+	xdev->pdev = pdev;
+
+	engine = xdev->engine_h2c;
+	for (i = 0; i < XDMA_CHANNEL_NUM_MAX; i++, engine++) {
+		spin_lock_init(&engine->lock);
+		mutex_init(&engine->desc_lock);
+		INIT_LIST_HEAD(&engine->transfer_list);
+		init_swait_queue_head(&engine->shutdown_wq);
+	}
+
+	engine = xdev->engine_c2h;
+	for (i = 0; i < XDMA_CHANNEL_NUM_MAX; i++, engine++) {
+		spin_lock_init(&engine->lock);
+		mutex_init(&engine->desc_lock);
+		INIT_LIST_HEAD(&engine->transfer_list);
+		init_swait_queue_head(&engine->shutdown_wq);
+	}
+
+	return xdev;
+}
+
+static int set_dma_mask(struct xdma_dev *xdev)
+{
+	if (!dma_set_mask(&xdev->pdev->dev, DMA_BIT_MASK(64))) {
+		pr_devel("XDMA: Using a 64-bit DMA mask\n");
+		/* use 32-bit DMA for descriptors */
+		dma_set_coherent_mask(&xdev->pdev->dev, DMA_BIT_MASK(32));
+	} else if (!dma_set_mask(&xdev->pdev->dev, DMA_BIT_MASK(32))) {
+		pr_devel("XDMA: Using a 32-bit DMA mask\n");
+		dma_set_coherent_mask(&xdev->pdev->dev, DMA_BIT_MASK(32));
+	} else {
+		pr_err("XDMA: No suitable DMA possible.\n");
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int get_engine_channel_id(struct engine_regs *regs)
+{
+	int value = ioread32(&regs->identifier);
+
+	return (value & 0x00000f00U) >> 8;
+}
+
+static int get_engine_id(struct engine_regs *regs)
+{
+	int value = ioread32(&regs->identifier);
+
+	return (value & 0xffff0000U) >> 16;
+}
+
+static void remove_engines(struct xdma_dev *xdev, int h2c_channel_max,
+			   int c2h_channel_max)
+{
+	int i;
+
+	for (i = 0; i < h2c_channel_max; i++)
+		engine_destroy(xdev, &xdev->engine_h2c[i]);
+
+	for (i = 0; i < c2h_channel_max; i++)
+		engine_destroy(xdev, &xdev->engine_c2h[i]);
+}
+
+static int probe_for_engine(struct xdma_dev *xdev, enum dma_data_direction dir,
+			    int channel)
+{
+	struct engine_regs *regs;
+	int offset = channel * CHANNEL_SPACING;
+	u32 engine_id;
+	u32 engine_id_expected;
+	u32 channel_id;
+	struct xdma_engine *engine;
+
+	if (dir == DMA_TO_DEVICE) {
+		engine_id_expected = XDMA_ID_H2C;
+		engine = &xdev->engine_h2c[channel];
+	} else {
+		offset += H2C_CHANNEL_OFFSET;
+		engine_id_expected = XDMA_ID_C2H;
+		engine = &xdev->engine_c2h[channel];
+	}
+
+	regs = xdev->config_bar + offset;
+	engine_id = get_engine_id(regs);
+	channel_id = get_engine_channel_id(regs);
+
+	if ((engine_id != engine_id_expected) || (channel_id != channel)) {
+		pr_err("XDMA: %s engine #%d not found\n",
+		       dir == DMA_TO_DEVICE ? "H2C" : "C2H", channel);
+		return -EINVAL;
+	}
+
+	engine_init(engine, xdev, offset, dir, channel);
+
+	return 0;
+}
+
+static int probe_engines(struct xdma_dev *xdev, int h2c_channel_max,
+			 int c2h_channel_max)
+{
+	int i, rv;
+
+	for (i = 0; i < h2c_channel_max; i++) {
+		rv = probe_for_engine(xdev, DMA_TO_DEVICE, i);
+		if (rv)
+			return rv;
+	}
+
+	for (i = 0; i < c2h_channel_max; i++) {
+		rv = probe_for_engine(xdev, DMA_FROM_DEVICE, i);
+		if (rv)
+			return rv;
+	}
+
+	return 0;
+}
+
+
+int xdma_probe(struct xdma_core *xdma)
+{
+	int rv;
+
+	if (xdma->user_irq_max > MAX_USER_IRQ) {
+		pr_err("XDMA: %d: Invalid number of user IRQs\n",
+		       xdma->user_irq_max);
+		return -EINVAL;
+	}
+	if (xdma->h2c_channel_max > XDMA_CHANNEL_NUM_MAX) {
+		pr_err("XDMA: %d: Invalid number of H2C channels\n",
+		       xdma->h2c_channel_max);
+		return -EINVAL;
+	}
+	if (xdma->c2h_channel_max > XDMA_CHANNEL_NUM_MAX) {
+		pr_err("XDMA: %d: Invalid number of C2H channels\n",
+		       xdma->c2h_channel_max);
+		return -EINVAL;
+	}
+
+	xdma->xdev = alloc_dev(xdma->pdev);
+	if (!xdma->xdev)
+		return -ENOMEM;
+
+	rv = map_config_bar(xdma->xdev, xdma->config_bar_id);
+	if (rv)
+		goto err_map;
+
+	rv = set_dma_mask(xdma->xdev);
+	if (rv)
+		goto err_mask;
+
+	channel_interrupts_disable(xdma->xdev, ~0);
+	user_interrupts_disable(xdma->xdev, ~0);
+	/* Flush writes */
+	read_interrupts(xdma->xdev);
+
+	rv = probe_engines(xdma->xdev, xdma->h2c_channel_max, xdma->c2h_channel_max);
+	if (rv)
+		goto err_engines;
+
+	rv = irq_setup(xdma->xdev, xdma->h2c_channel_max, xdma->c2h_channel_max,
+		       xdma->user_irq_max);
+	if (rv < 0)
+		goto err_interrupts;
+	channel_interrupts_enable(xdma->xdev, ~0);
+	/* Flush writes */
+	read_interrupts(xdma->xdev);
+
+	return 0;
+
+err_interrupts:
+	irq_teardown(xdma->xdev, xdma->h2c_channel_max, xdma->c2h_channel_max,
+		     xdma->user_irq_max);
+err_engines:
+	remove_engines(xdma->xdev, xdma->h2c_channel_max, xdma->c2h_channel_max);
+err_mask:
+	unmap_config_bar(xdma->xdev, xdma->config_bar_id);
+err_map:
+	kfree(xdma->xdev);
+
+	return rv;
+}
+EXPORT_SYMBOL_GPL(xdma_probe);
+
+void xdma_remove(struct xdma_core *xdma)
+{
+	channel_interrupts_disable(xdma->xdev, ~0);
+	user_interrupts_disable(xdma->xdev, ~0);
+	/* Flush writes */
+	read_interrupts(xdma->xdev);
+
+	irq_teardown(xdma->xdev, xdma->h2c_channel_max, xdma->c2h_channel_max,
+		     xdma->user_irq_max);
+
+	remove_engines(xdma->xdev, xdma->h2c_channel_max, xdma->c2h_channel_max);
+	unmap_config_bar(xdma->xdev, xdma->config_bar_id);
+
+	kfree(xdma->xdev);
+}
+EXPORT_SYMBOL_GPL(xdma_remove);
+
+/**
+ * xdma_irq_enable - enable XDMA user interrupt(s)
+ * @xdma: XDMA device handle
+ * @mask: bitmask of user interrupts (0 ~ 15) to be registered
+ */
+void xdma_irq_enable(struct xdma_core *xdma, unsigned int mask)
+{
+	xdma->xdev->mask_irq_user |= mask;
+	user_interrupts_enable(xdma->xdev, mask);
+	/* Flush writes */
+	read_interrupts(xdma->xdev);
+}
+EXPORT_SYMBOL_GPL(xdma_irq_enable);
+
+/**
+ * xdma_irq_disable - disable XDMA user interrupt(s)
+ * @xdma: XDMA device handle
+ * @mask: bitmask of user interrupts (0 ~ 15) to be unregistered
+ */
+void xdma_irq_disable(struct xdma_core *xdma, unsigned int mask)
+{
+	xdma->xdev->mask_irq_user &= ~mask;
+	user_interrupts_disable(xdma->xdev, mask);
+	/* Flush writes */
+	read_interrupts(xdma->xdev);
+}
+EXPORT_SYMBOL_GPL(xdma_irq_disable);
+
+/**
+ * xdma_transfer - do a DMA transfer
+ * @xdma: XDMA device handle
+ * @channel: channel number
+ * @write: slecets read/write operation
+ * @ep_addr: offset into the DDR/BRAM (card) memory to read from or write to
+ * @sg_table: the scatter-gather list of data buffers
+ * @timeout_ms: timeout in mili-seconds
+ *
+ * Returns # of bytes transferred on success, negative on failure
+ */
+ssize_t xdma_transfer(struct xdma_core *xdma, int channel, bool write,
+		      u64 ep_addr, struct sg_table *sgt, int timeout_ms)
+{
+	struct xdma_engine *engine;
+	int rv = 0, i, nents;
+	ssize_t done = 0;
+	struct xdma_request_cb *req = NULL;
+
+
+	engine = channel_engine(xdma, channel, write);
+	if (!engine)
+		return -EINVAL;
+
+	req = xdma_init_request(xdma->xdev, sgt, ep_addr);
+	if (!req)
+		return -ENOMEM;
+
+	nents = req->sw_desc_cnt;
+	mutex_lock(&engine->desc_lock);
+
+	while (nents) {
+		unsigned long flags;
+		struct xdma_transfer *xfer;
+
+		/* build transfer */
+		transfer_init(engine, req, &req->tfer);
+		xfer = &req->tfer;
+
+		/* last transfer for the given request? */
+		nents -= xfer->desc_num;
+		if (!nents) {
+			xfer->last_in_request = 1;
+			xfer->sgt = sgt;
+		}
+
+		rv = transfer_queue(engine, xfer);
+		if (rv < 0)
+			break;
+
+		if (timeout_ms > 0)
+			swait_event_interruptible_timeout_exclusive(xfer->wq,
+				(xfer->state != TRANSFER_STATE_SUBMITTED),
+				msecs_to_jiffies(timeout_ms));
+		else
+			swait_event_interruptible_exclusive(xfer->wq,
+				(xfer->state != TRANSFER_STATE_SUBMITTED));
+
+		spin_lock_irqsave(&engine->lock, flags);
+
+		switch (xfer->state) {
+		case TRANSFER_STATE_COMPLETED:
+			spin_unlock_irqrestore(&engine->lock, flags);
+			/* For C2H streaming use writeback results */
+			if (engine->streaming &&
+			    engine->dir == DMA_FROM_DEVICE) {
+				struct xdma_result *result = xfer->res_virt;
+
+				for (i = 0; i < xfer->desc_cmpl; i++)
+					done += result[i].length;
+
+				/* finish the whole request */
+				if (engine->eop_flush)
+					nents = 0;
+			} else
+				done += xfer->len;
+			rv = 0;
+			break;
+		case TRANSFER_STATE_FAILED:
+			pr_warn("XDMA: transfer failed\n");
+			spin_unlock_irqrestore(&engine->lock, flags);
+			rv = -EIO;
+			break;
+		default:
+			/* transfer can still be in-flight */
+			pr_warn("XDMA: transfer timed out\n");
+			engine_status_read(engine, 0, 1);
+			transfer_abort(engine, xfer);
+			engine_stop(engine);
+			spin_unlock_irqrestore(&engine->lock, flags);
+			rv = -ERESTARTSYS;
+			break;
+		}
+
+		engine->desc_used -= xfer->desc_num;
+		transfer_destroy(xdma->xdev, xfer);
+
+		if (rv < 0)
+			break;
+	}
+
+	mutex_unlock(&engine->desc_lock);
+	xdma_request_free(req);
+
+	return rv ? rv : done;
+}
+EXPORT_SYMBOL_GPL(xdma_transfer);
+
+MODULE_AUTHOR("Digiteq Automotive s.r.o.");
+MODULE_DESCRIPTION("Xilinx XDMA Driver");
+MODULE_LICENSE("GPL");
diff --git a/include/linux/dma/xilinx_xdma.h b/include/linux/dma/xilinx_xdma.h
new file mode 100644
index 000000000000..c63dc7768e66
--- /dev/null
+++ b/include/linux/dma/xilinx_xdma.h
@@ -0,0 +1,44 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * This file is part of the Xilinx DMA IP Core driver for Linux
+ *
+ * Copyright (c) 2016-2021,  Xilinx, Inc.
+ * Copyright (c) 2020-2022,  Digiteq Automotive s.r.o.
+ */
+
+#ifndef XILINX_XDMA_H
+#define XILINX_XDMA_H
+
+#include <linux/pci.h>
+#include <linux/scatterlist.h>
+
+struct xdma_dev;
+
+/**
+ * struct xdma_core - representation of XDMA hardware
+ * @pdev:		The parent PCIe device which contains the XDMA core
+ * @config_bar_id:	PCI BAR id where XDMA config regs are located
+ * @user_irq_max:	number of user IRQs
+ * @c2h_channel_max:	number of C2H DMA channels
+ * @h2c_channel_max:	number of H2C DMA channels
+ * @xdev:		struct xdma_dev that is filed by ->probe()
+ */
+struct xdma_core {
+	struct pci_dev *pdev;
+	int config_bar_id;
+	unsigned int user_irq_max;
+	unsigned int c2h_channel_max;
+	unsigned int h2c_channel_max;
+	struct xdma_dev *xdev;
+};
+
+int xdma_probe(struct xdma_core *xdma);
+void xdma_remove(struct xdma_core *xdma);
+
+void xdma_irq_enable(struct xdma_core *xdma, unsigned int mask);
+void xdma_irq_disable(struct xdma_core *xdma, unsigned int mask);
+
+ssize_t xdma_transfer(struct xdma_core *xdma, int channel, bool write,
+		      u64 ep_addr, struct sg_table *sgt, int timeout_ms);
+
+#endif /* XILINX_XDMA_H */
-- 
2.37.2


^ permalink raw reply related	[flat|nested] 13+ messages in thread

* [PATCH v2 3/3] Added Digiteq Automotive MGB4 driver
  2022-09-19 18:55 [PATCH v2 0/3] Digiteq Automotive MGB4 driver tumic
  2022-09-19 18:55 ` [PATCH v2 1/3] Added platform module alias for the xiic I2C driver tumic
  2022-09-19 18:55 ` [PATCH v2 2/3] Added Xilinx XDMA IP core driver tumic
@ 2022-09-19 18:55 ` tumic
  2022-10-03  5:19   ` kernel test robot
  2022-09-21  5:32 ` [PATCH v2 0/3] " Sonal Santan
  3 siblings, 1 reply; 13+ messages in thread
From: tumic @ 2022-09-19 18:55 UTC (permalink / raw)
  To: Mauro Carvalho Chehab, Vinod Koul, Michal Simek
  Cc: linux-media, linux-kernel, dmaengine, linux-i2c, Martin Tůma

From: Martin Tůma <martin.tuma@digiteqautomotive.com>

Digiteq Automotive MGB4 is a modular frame grabber PCIe card for automotive
video interfaces. As for now, two modules - FPD-Link and GMSL - are
available and supported by the driver. The card has two inputs and two
outputs (FPD-Link only).

In addition to the video interfaces it also provides a trigger signal
interface and a MTD interface for FPGA firmware upload.

Signed-off-by: Martin Tůma <martin.tuma@digiteqautomotive.com>
---
 Documentation/admin-guide/media/mgb4-iio.rst  |  30 +
 Documentation/admin-guide/media/mgb4-mtd.rst  |  16 +
 .../admin-guide/media/mgb4-sysfs.rst          | 297 +++++++
 drivers/media/pci/Kconfig                     |   1 +
 drivers/media/pci/Makefile                    |   1 +
 drivers/media/pci/mgb4/Kconfig                |  17 +
 drivers/media/pci/mgb4/Makefile               |   6 +
 drivers/media/pci/mgb4/mgb4_cmt.c             | 243 ++++++
 drivers/media/pci/mgb4/mgb4_cmt.h             |  16 +
 drivers/media/pci/mgb4/mgb4_core.c            | 554 +++++++++++++
 drivers/media/pci/mgb4/mgb4_core.h            |  58 ++
 drivers/media/pci/mgb4/mgb4_i2c.c             | 139 ++++
 drivers/media/pci/mgb4/mgb4_i2c.h             |  35 +
 drivers/media/pci/mgb4/mgb4_io.h              |  36 +
 drivers/media/pci/mgb4/mgb4_regs.c            |  30 +
 drivers/media/pci/mgb4/mgb4_regs.h            |  35 +
 drivers/media/pci/mgb4/mgb4_sysfs.h           |  18 +
 drivers/media/pci/mgb4/mgb4_sysfs_in.c        | 750 ++++++++++++++++++
 drivers/media/pci/mgb4/mgb4_sysfs_out.c       | 734 +++++++++++++++++
 drivers/media/pci/mgb4/mgb4_sysfs_pci.c       |  83 ++
 drivers/media/pci/mgb4/mgb4_trigger.c         | 202 +++++
 drivers/media/pci/mgb4/mgb4_trigger.h         |   8 +
 drivers/media/pci/mgb4/mgb4_vin.c             | 656 +++++++++++++++
 drivers/media/pci/mgb4/mgb4_vin.h             |  64 ++
 drivers/media/pci/mgb4/mgb4_vout.c            | 502 ++++++++++++
 drivers/media/pci/mgb4/mgb4_vout.h            |  58 ++
 26 files changed, 4589 insertions(+)
 create mode 100644 Documentation/admin-guide/media/mgb4-iio.rst
 create mode 100644 Documentation/admin-guide/media/mgb4-mtd.rst
 create mode 100644 Documentation/admin-guide/media/mgb4-sysfs.rst
 create mode 100644 drivers/media/pci/mgb4/Kconfig
 create mode 100644 drivers/media/pci/mgb4/Makefile
 create mode 100644 drivers/media/pci/mgb4/mgb4_cmt.c
 create mode 100644 drivers/media/pci/mgb4/mgb4_cmt.h
 create mode 100644 drivers/media/pci/mgb4/mgb4_core.c
 create mode 100644 drivers/media/pci/mgb4/mgb4_core.h
 create mode 100644 drivers/media/pci/mgb4/mgb4_i2c.c
 create mode 100644 drivers/media/pci/mgb4/mgb4_i2c.h
 create mode 100644 drivers/media/pci/mgb4/mgb4_io.h
 create mode 100644 drivers/media/pci/mgb4/mgb4_regs.c
 create mode 100644 drivers/media/pci/mgb4/mgb4_regs.h
 create mode 100644 drivers/media/pci/mgb4/mgb4_sysfs.h
 create mode 100644 drivers/media/pci/mgb4/mgb4_sysfs_in.c
 create mode 100644 drivers/media/pci/mgb4/mgb4_sysfs_out.c
 create mode 100644 drivers/media/pci/mgb4/mgb4_sysfs_pci.c
 create mode 100644 drivers/media/pci/mgb4/mgb4_trigger.c
 create mode 100644 drivers/media/pci/mgb4/mgb4_trigger.h
 create mode 100644 drivers/media/pci/mgb4/mgb4_vin.c
 create mode 100644 drivers/media/pci/mgb4/mgb4_vin.h
 create mode 100644 drivers/media/pci/mgb4/mgb4_vout.c
 create mode 100644 drivers/media/pci/mgb4/mgb4_vout.h

diff --git a/Documentation/admin-guide/media/mgb4-iio.rst b/Documentation/admin-guide/media/mgb4-iio.rst
new file mode 100644
index 000000000000..8e708ddd1b15
--- /dev/null
+++ b/Documentation/admin-guide/media/mgb4-iio.rst
@@ -0,0 +1,30 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+====================
+mgb4 iio (triggers)
+====================
+
+The mgb4 driver creates an Industrial I/O (IIO) device that provides trigger and
+signal level status capability. The following scan elements are available:
+
+**activity**:
+	The trigger levels and pending status.
+
+	| bit 1 - trigger 1 pending
+	| bit 2 - trigger 2 pending
+	| bit 5 - trigger 1 level
+	| bit 6 - trigger 2 level
+
+**timestamp**:
+	The trigger event timestamp.
+
+The iio device can operate either in "raw" mode where you can fetch the signal
+levels (activity bits 5 and 6) using sysfs access or in triggered buffer mode.
+In the triggered buffer mode you can follow the signal level changes (activity
+bits 1 and 2) using the iio device in /dev. If you enable the timestamps, you
+will also get the exact trigger event time that can be matched to a video frame
+(every mgb4 video frame has a timestamp with the same clock source).
+
+*Note: although the activity sample always contains all the status bits, it makes
+no sense to get the pending bits in raw mode or the level bits in the triggered
+buffer mode - the values do not represent valid data in such case.*
diff --git a/Documentation/admin-guide/media/mgb4-mtd.rst b/Documentation/admin-guide/media/mgb4-mtd.rst
new file mode 100644
index 000000000000..ca20b799f00f
--- /dev/null
+++ b/Documentation/admin-guide/media/mgb4-mtd.rst
@@ -0,0 +1,16 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+====================
+mgb4 mtd partitions
+====================
+
+The mgb4 driver creates a MTD device with two partitions:
+ - mgb4-fw.X - FPGA firmware.
+ - mgb4-data.X - Factory settings, e.g. card serial number.
+
+The *mgb4-fw* partition is writable and is used for FW updates, *mgb4-data* is
+read-only. The *X* attached to the partition name represents the card number.
+Depending on the CONFIG_MTD_PARTITIONED_MASTER kernel configuration, you may
+also have a third partition named *mgb4-flash* available in the system. This
+partition represents the whole, unpartitioned, card's FLASH memory and one should
+not fiddle with it...
diff --git a/Documentation/admin-guide/media/mgb4-sysfs.rst b/Documentation/admin-guide/media/mgb4-sysfs.rst
new file mode 100644
index 000000000000..21ff1b5d026e
--- /dev/null
+++ b/Documentation/admin-guide/media/mgb4-sysfs.rst
@@ -0,0 +1,297 @@
+.. SPDX-License-Identifier: GPL-2.0
+
+====================
+mgb4 sysfs interface
+====================
+
+The mgb4 driver provides a sysfs interface, that is used to configure video
+stream related parameters (some of them must be set properly before the v4l2
+device can be opened) and obtain the video device/stream status.
+
+There are two types of parameters - global / PCI card related, found under
+``/sys/class/video4linux/videoX/device`` and module specific found under
+``/sys/class/video4linux/videoX``.
+
+
+Global (PCI card) parameters
+============================
+
+**module_type** (R):
+    Module type.
+
+    | 0 - No module present
+    | 1 - FPDL3
+    | 2 - GMSL
+
+**module_version** (R):
+    Module version number. Zero in case of a missing module.
+
+**fw_type** (R):
+    Firmware type.
+
+    | 1 - FPDL3
+    | 2 - GMSL
+
+**fw_version** (R):
+    Firmware version number.
+
+**serial_number** (R):
+    Card serial number. The format is::
+
+        PRODUCT-REVISION-SERIES-SERIAL
+
+    where each component is a 8b number.
+
+**temperature** (R):
+    FPGA core temperature in Celsius degree.
+
+Common FPDL3/GMSL input parameters
+==================================
+
+**input_id** (R):
+    Input number ID, zero based.
+
+**oldi_lane_width** (RW):
+    Number of deserializer output lanes.
+
+    | 0 - single
+    | 1 - dual
+
+**color_mapping** (RW):
+    Mapping of the incoming bits in the signal to the colour bits of the pixels.
+
+    | 0 - OLDI/JEIDA
+    | 1 - SPWG/VESA
+
+**link_status** (R):
+    Video link status. If the link is locked, chips are properly connected and
+    communicating at the same speed and protocol. The link can be locked without
+    an active video stream.
+
+    | 0 - unlocked
+    | 1 - locked
+
+**stream_status** (R):
+    Video stream status. A stream is detected if the link is locked, the input
+    pixel clock is running and the DE signal is moving.
+
+    | 0 - not detected
+    | 1 - detected
+
+**vsync_status** (R):
+    The type of VSYNC pulses as detected by the video format detector.
+
+    | 0 - active low
+    | 1 - active high
+    | 2 - not available
+
+**hsync_status** (R):
+    The type of HSYNC pulses as detected by the video format detector.
+
+    | 0 - active low
+    | 1 - active high
+    | 2 - not available
+
+**vsync_gap_length** (RW):
+    If the incoming video signal does not contain synchronization VSYNC and
+    HSYNC pulses, these must be generated internally in the FPGA to achieve
+    the correct frame ordering. This value indicates, how many "empty" pixels
+    (pixels with deasserted Data Enable signal) are necessary to generate the
+    internal VSYNC pulse.
+
+**hsync_gap_length** (RW):
+    If the incoming video signal does not contain synchronization VSYNC and
+    HSYNC pulses, these must be generated internally in the FPGA to achieve
+    the correct frame ordering. This value indicates, how many "empty" pixels
+    (pixels with deasserted Data Enable signal) are necessary to generate the
+    internal HSYNC pulse. The value must be greater than 1 and smaller than
+    vsync_gap_length.
+
+**pclk_frequency** (R):
+    Input pixel clock frequency in kHz.
+
+    *Note: The frequency_range parameter must be set properly first to get
+    a valid frequency here.*
+
+**hsync_width** (R):
+    Width of the HSYNC signal in PCLK clock ticks.
+
+**vsync_width** (R):
+    Width of the VSYNC signal in PCLK clock ticks.
+
+**hback_porch** (R):
+    Number of PCLK pulses between deassertion of the HSYNC signal and the first
+    valid pixel in the video line (marked by DE=1).
+
+**hfront_porch** (R):
+    Number of PCLK pulses between the end of the last valid pixel in the video
+    line (marked by DE=1) and assertion of the HSYNC signal.
+
+**vback_porch** (R):
+    Number of video lines between deassertion of the VSYNC signal and the video
+    line with the first valid pixel (marked by DE=1).
+
+**vfront_porch** (R):
+    Number of video lines between the end of the last valid pixel line (marked
+    by DE=1) and assertion of the VSYNC signal.
+
+**frequency_range** (RW)
+    PLL frequency range of the OLDI input clock generator. The PLL frequency is
+    derived from the Pixel Clock Frequency (PCLK) and is equal to PCLK if
+    oldi_lane_width is set to "single" and PCLK/2 if oldi_lane_width is set to
+    "dual".
+
+    | 0 - PLL < 50MHz
+    | 1 - PLL >= 50MHz
+
+    *Note: This parameter can not be changed while the input v4l2 device is
+    open.*
+
+**alignment** (RW)
+    Pixel line alignment. Sets the pixel line alignment in bytes of the frame
+    buffers provided via the v4l2 interface. The number must be a power of 2.
+
+    *Note: This parameter can not be changed while the input v4l2 device is
+    open.*
+
+Common FPDL3/GMSL output parameters
+===================================
+
+**output_id** (R):
+    Output number ID, zero based.
+
+**video_source** (RW):
+    Output video source. If set to 0 or 1, the source is the corresponding card
+    input and the v4l2 output devices are disabled. If set to 2 or 3, the source
+    is the corresponding v4l2 video output device.
+
+    | 0 - input 0
+    | 1 - input 1
+    | 2 - v4l2 output 0
+    | 3 - v4l2 output 1
+
+    *Note: This parameter can not be changed while ANY of the input/output v4l2
+    devices is open.*
+
+**display_width** (RW):
+    Display width. There is no autodetection of the connected display, so the
+    propper value must be set before the start of streaming.
+
+    *Note: This parameter can not be changed while the output v4l2 device is
+    open.*
+
+**display_height** (RW):
+    Display height. There is no autodetection of the connected display, so the
+    propper value must be set before the start of streaming.
+
+    *Note: This parameter can not be changed while the output v4l2 device is
+    open.*
+
+**frame_rate** (RW):
+    Output video frame rate in frames per second.
+
+**hsync_polarity** (RW):
+    HSYNC signal polarity.
+
+    | 0 - active low
+    | 1 - active high
+
+**vsync_polarity** (RW):
+    VSYNC signal polarity.
+
+    | 0 - active low
+    | 1 - active high
+
+**de_polarity** (RW):
+    DE signal polarity.
+
+    | 0 - active low
+    | 1 - active high
+
+**pclk_frequency** (RW):
+    Output pixel clock frequency. Allowed values are between 25000-190000(kHz)
+    and there is a non-linear stepping between two consecutive allowed
+    frequencies. The driver finds the nearest allowed frequency to the given
+    value and sets it. When reading this property, you get the exact
+    frequency set by the driver.
+
+    *Note: This parameter can not be changed while the output v4l2 device is
+    open.*
+
+**hsync_width** (RW):
+    Width of the HSYNC signal in PCLK clock ticks.
+
+**vsync_width** (RW):
+    Width of the VSYNC signal in PCLK clock ticks.
+
+**hback_porch** (RW):
+    Number of PCLK pulses between deassertion of the HSYNC signal and the first
+    valid pixel in the video line (marked by DE=1).
+
+**hfront_porch** (RW):
+    Number of PCLK pulses between the end of the last valid pixel in the video
+    line (marked by DE=1) and assertion of the HSYNC signal.
+
+**vback_porch** (RW):
+    Number of video lines between deassertion of the VSYNC signal and the video
+    line with the first valid pixel (marked by DE=1).
+
+**vfront_porch** (RW):
+    Number of video lines between the end of the last valid pixel line (marked
+    by DE=1) and assertion of the VSYNC signal.
+
+**alignment** (RW)
+    Pixel line alignment. Sets the pixel line alignment in bytes of the frame
+    buffers provided via the v4l2 interface. The number must be a power of 2.
+
+    *Note: This parameter can not be changed while the output v4l2 device is
+    open.*
+
+    *Note: This parameter can not be changed when loopback mode is active
+    (video_source is 0 or 1). When loopback mode is enabled, the alignment is
+    automatically set to the alignment of the input device.*
+
+FPDL3 specific input parameters
+===============================
+
+**fpdl3_input_width** (RW):
+    Number of deserializer input lines.
+
+    | 0 - auto
+    | 1 - single
+    | 2 - dual
+
+FPDL3 specific output parameters
+================================
+
+**fpdl3_output_width** (RW):
+    Number of serializer output lines.
+
+    | 0 - auto
+    | 1 - single
+    | 2 - dual
+
+GMSL specific input parameters
+==============================
+
+**gmsl_mode** (RW):
+    GMSL speed mode.
+
+    | 0 - 12Gb/s
+    | 1 - 6Gb/s
+    | 2 - 3Gb/s
+    | 3 - 1.5Gb/s
+
+**gmsl_stream_id** (RW):
+    The GMSL multi-stream contains up to four video streams. This parameter
+    selects which stream is captured by the video input. The value is the
+    zero-based index of the stream.
+
+    *Note: This parameter can not be changed while the input v4l2 device is
+    open.*
+
+**gmsl_fec** (RW):
+    GMSL Forward Error Correction (FEC).
+
+    | 0 - disabled
+    | 1 - enabled
diff --git a/drivers/media/pci/Kconfig b/drivers/media/pci/Kconfig
index 1224d908713a..e3f9d0d4e4cc 100644
--- a/drivers/media/pci/Kconfig
+++ b/drivers/media/pci/Kconfig
@@ -13,6 +13,7 @@ if MEDIA_PCI_SUPPORT
 if MEDIA_CAMERA_SUPPORT
 	comment "Media capture support"
 
+source "drivers/media/pci/mgb4/Kconfig"
 source "drivers/media/pci/meye/Kconfig"
 source "drivers/media/pci/solo6x10/Kconfig"
 source "drivers/media/pci/sta2x11/Kconfig"
diff --git a/drivers/media/pci/Makefile b/drivers/media/pci/Makefile
index 551169a3e434..8ca819cf3cc1 100644
--- a/drivers/media/pci/Makefile
+++ b/drivers/media/pci/Makefile
@@ -33,6 +33,7 @@ obj-$(CONFIG_VIDEO_CX88) += cx88/
 obj-$(CONFIG_VIDEO_DT3155) += dt3155/
 obj-$(CONFIG_VIDEO_IVTV) += ivtv/
 obj-$(CONFIG_VIDEO_MEYE) += meye/
+obj-$(CONFIG_VIDEO_MGB4) += mgb4/
 obj-$(CONFIG_VIDEO_SAA7134) += saa7134/
 obj-$(CONFIG_VIDEO_SAA7164) += saa7164/
 obj-$(CONFIG_VIDEO_SOLO6X10) += solo6x10/
diff --git a/drivers/media/pci/mgb4/Kconfig b/drivers/media/pci/mgb4/Kconfig
new file mode 100644
index 000000000000..13fad15a434c
--- /dev/null
+++ b/drivers/media/pci/mgb4/Kconfig
@@ -0,0 +1,17 @@
+# SPDX-License-Identifier: GPL-2.0-only
+config VIDEO_MGB4
+	tristate "Digiteq Automotive MGB4 support"
+	depends on VIDEO_DEV && PCI && I2C && DMADEVICES && SPI && MTD && IIO
+	select VIDEOBUF2_DMA_SG
+	select IIO_BUFFER
+	select IIO_TRIGGERED_BUFFER
+	select I2C_XILINX
+	select SPI_XILINX
+	select MTD_SPI_NOR
+	select XILINX_XDMA
+	help
+	  This is a video4linux driver for Digiteq Automotive MGB4 grabber
+	  cards.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called mgb4.
diff --git a/drivers/media/pci/mgb4/Makefile b/drivers/media/pci/mgb4/Makefile
new file mode 100644
index 000000000000..aeac053b8031
--- /dev/null
+++ b/drivers/media/pci/mgb4/Makefile
@@ -0,0 +1,6 @@
+# SPDX-License-Identifier: GPL-2.0
+mgb4-objs	:= mgb4_regs.o mgb4_core.o mgb4_vin.o mgb4_vout.o \
+               mgb4_sysfs_pci.o mgb4_sysfs_in.o mgb4_sysfs_out.o \
+               mgb4_i2c.o mgb4_cmt.o mgb4_trigger.o
+
+obj-$(CONFIG_VIDEO_MGB4) += mgb4.o
diff --git a/drivers/media/pci/mgb4/mgb4_cmt.c b/drivers/media/pci/mgb4/mgb4_cmt.c
new file mode 100644
index 000000000000..3ec394e46bd0
--- /dev/null
+++ b/drivers/media/pci/mgb4/mgb4_cmt.c
@@ -0,0 +1,243 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2021-2022 Digiteq Automotive
+ *     author: Martin Tuma <martin.tuma@digiteqautomotive.com>
+ */
+
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include "mgb4_core.h"
+#include "mgb4_cmt.h"
+
+static const uint16_t cmt_vals_out[][15] = {
+	{0x1208, 0x0000, 0x171C, 0x0000, 0x1E38, 0x0000, 0x11C7, 0x0000, 0x1041, 0x01BC, 0x7C01, 0x7DE9, 0xFFFF, 0x9900, 0x8100, },
+	{0x11C7, 0x0000, 0x1619, 0x0080, 0x1C71, 0x0000, 0x130D, 0x0080, 0x0041, 0x0090, 0x7C01, 0x7DE9, 0xFFFF, 0x1100, 0x9000, },
+	{0x11C7, 0x0000, 0x1619, 0x0080, 0x1C71, 0x0000, 0x165A, 0x0080, 0x0082, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x1000, 0x9000, },
+	{0x11C7, 0x0000, 0x1619, 0x0080, 0x1C71, 0x0000, 0x1187, 0x0080, 0x1041, 0x01EE, 0x7C01, 0x7DE9, 0xFFFF, 0x9900, 0x8100, },
+	{0x1186, 0x0000, 0x1555, 0x0000, 0x1AAA, 0x0000, 0x1451, 0x0000, 0x0042, 0x0013, 0x7C01, 0x7DE9, 0xFFFF, 0x0900, 0x1000, },
+	{0x11C7, 0x0000, 0x1619, 0x0080, 0x1C71, 0x0000, 0x134E, 0x0080, 0x0041, 0x005E, 0x7C01, 0x7DE9, 0xFFFF, 0x0900, 0x1000, },
+	{0x1145, 0x0000, 0x1452, 0x0080, 0x18E3, 0x0000, 0x1619, 0x0080, 0x0083, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x1000, 0x9000, },
+	{0x1145, 0x0000, 0x1452, 0x0080, 0x18E3, 0x0000, 0x179E, 0x0000, 0x00C3, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x0800, 0x1000, },
+	{0x1145, 0x0000, 0x1452, 0x0080, 0x18E3, 0x0000, 0x179F, 0x0080, 0x00C3, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x0800, 0x1000, },
+	{0x1145, 0x0000, 0x1452, 0x0080, 0x18E3, 0x0000, 0x17DF, 0x0000, 0x00C3, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x1000, 0x8800, },
+	{0x1145, 0x0000, 0x1452, 0x0080, 0x18E3, 0x0000, 0x128B, 0x0080, 0x0041, 0x00DB, 0x7C01, 0x7DE9, 0xFFFF, 0x9000, 0x0100, },
+	{0x1186, 0x0000, 0x1555, 0x0000, 0x1AAA, 0x0000, 0x1820, 0x0000, 0x0083, 0x00FA, 0x7DE9, 0x7DE8, 0xFFFF, 0x0900, 0x9000, },
+	{0x1186, 0x0000, 0x1555, 0x0000, 0x1AAA, 0x0000, 0x1187, 0x0080, 0x1041, 0x01EE, 0x7C01, 0x7DE9, 0xFFFF, 0x9900, 0x8100, },
+	{0x1104, 0x0000, 0x138E, 0x0000, 0x171C, 0x0000, 0x169B, 0x0080, 0x00C3, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x1800, 0x0100, },
+	{0x1145, 0x0000, 0x1452, 0x0080, 0x18E3, 0x0000, 0x171C, 0x0000, 0x0083, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x1100, 0x1800, },
+	{0x1186, 0x0000, 0x1555, 0x0000, 0x1AAA, 0x0000, 0x1515, 0x0080, 0x0042, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x0900, 0x1000, },
+	{0x1104, 0x0000, 0x138E, 0x0000, 0x171C, 0x0000, 0x1493, 0x0080, 0x0082, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x0900, 0x1000, },
+	{0x1145, 0x0000, 0x1452, 0x0080, 0x18E3, 0x0000, 0x15D8, 0x0080, 0x0082, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x1900, 0x0100, },
+	{0x1104, 0x0000, 0x138E, 0x0000, 0x171C, 0x0000, 0x124A, 0x0080, 0x0041, 0x010D, 0x7C01, 0x7DE9, 0xFFFF, 0x9000, 0x0100, },
+	{0x1104, 0x0000, 0x138E, 0x0000, 0x171C, 0x0000, 0x175D, 0x0000, 0x00C3, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x0800, 0x1000, },
+	{0x1145, 0x0000, 0x1452, 0x0080, 0x18E3, 0x0000, 0x1619, 0x0080, 0x0082, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x1000, 0x9000, },
+	{0x1145, 0x0000, 0x1452, 0x0080, 0x18E3, 0x0000, 0x17DF, 0x0000, 0x0083, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x1000, 0x8800, },
+	{0x1145, 0x0000, 0x1452, 0x0080, 0x18E3, 0x0000, 0x17E0, 0x0080, 0x0083, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x0900, 0x9000, },
+	{0x1145, 0x0000, 0x1452, 0x0080, 0x18E3, 0x0000, 0x1820, 0x0000, 0x0083, 0x00FA, 0x7DE9, 0x7DE8, 0xFFFF, 0x0900, 0x9000, },
+	{0x1104, 0x0000, 0x138E, 0x0000, 0x171C, 0x0000, 0x13D0, 0x0080, 0x0042, 0x002C, 0x7C01, 0x7DE9, 0xFFFF, 0x0900, 0x1000, },
+	{0x1104, 0x0000, 0x138E, 0x0000, 0x171C, 0x0000, 0x128B, 0x0080, 0x0041, 0x00DB, 0x7C01, 0x7DE9, 0xFFFF, 0x9000, 0x0100, },
+	{0x1104, 0x0000, 0x138E, 0x0000, 0x171C, 0x0000, 0x1820, 0x0000, 0x00C3, 0x00FA, 0x7DE9, 0x7DE8, 0xFFFF, 0x0900, 0x9000, },
+	{0x1145, 0x0000, 0x1452, 0x0080, 0x18E3, 0x0000, 0x134E, 0x0080, 0x0041, 0x005E, 0x7C01, 0x7DE9, 0xFFFF, 0x0900, 0x1000, },
+	{0x1145, 0x0000, 0x1452, 0x0080, 0x18E3, 0x0000, 0x1515, 0x0080, 0x0042, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x0900, 0x1000, },
+	{0x10C3, 0x0000, 0x128B, 0x0080, 0x1555, 0x0000, 0x175D, 0x0000, 0x00C4, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x0800, 0x1000, },
+	{0x1145, 0x0000, 0x1452, 0x0080, 0x18E3, 0x0000, 0x11C7, 0x0000, 0x1041, 0x01BC, 0x7C01, 0x7DE9, 0xFFFF, 0x9900, 0x8100, },
+	{0x10C3, 0x0000, 0x128B, 0x0080, 0x1555, 0x0000, 0x1209, 0x0080, 0x0041, 0x013F, 0x7C01, 0x7DE9, 0xFFFF, 0x9900, 0x1100, },
+	{0x10C3, 0x0000, 0x128B, 0x0080, 0x1555, 0x0000, 0x1556, 0x0080, 0x0083, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x0800, 0x8000, },
+	{0x10C3, 0x0000, 0x128B, 0x0080, 0x1555, 0x0000, 0x179F, 0x0080, 0x00C4, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x0800, 0x1000, },
+	{0x1104, 0x0000, 0x138E, 0x0000, 0x171C, 0x0000, 0x15D8, 0x0080, 0x0082, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x1900, 0x0100, },
+	{0x10C3, 0x0000, 0x128B, 0x0080, 0x1555, 0x0000, 0x1105, 0x0080, 0x1041, 0x01E8, 0x6401, 0x65E9, 0xFFFF, 0x9800, 0x1100, },
+	{0x10C3, 0x0000, 0x128B, 0x0080, 0x1555, 0x0000, 0x1820, 0x0000, 0x00C4, 0x00FA, 0x7DE9, 0x7DE8, 0xFFFF, 0x0900, 0x9000, },
+	{0x1104, 0x0000, 0x138E, 0x0000, 0x171C, 0x0000, 0x1493, 0x0080, 0x0042, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x0900, 0x1000, },
+	{0x10C3, 0x0000, 0x128B, 0x0080, 0x1555, 0x0000, 0x138E, 0x0000, 0x0042, 0x005E, 0x7C01, 0x7DE9, 0xFFFF, 0x0900, 0x1000, },
+	{0x1104, 0x0000, 0x138E, 0x0000, 0x171C, 0x0000, 0x17E0, 0x0080, 0x0083, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x0900, 0x9000, },
+	{0x1104, 0x0000, 0x138E, 0x0000, 0x171C, 0x0000, 0x165A, 0x0080, 0x0082, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x1000, 0x9000, },
+	{0x10C3, 0x0000, 0x128B, 0x0080, 0x1555, 0x0000, 0x175D, 0x0000, 0x00C3, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x0800, 0x1000, },
+	{0x1104, 0x0000, 0x138E, 0x0000, 0x171C, 0x0000, 0x1187, 0x0080, 0x1041, 0x01EE, 0x7C01, 0x7DE9, 0xFFFF, 0x9900, 0x8100, },
+	{0x10C3, 0x0000, 0x128B, 0x0080, 0x1555, 0x0000, 0x175E, 0x0080, 0x00C3, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x0800, 0x1000, },
+	{0x10C3, 0x0000, 0x128B, 0x0080, 0x1555, 0x0000, 0x179E, 0x0000, 0x00C3, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x0800, 0x1000, },
+	{0x1104, 0x0000, 0x138E, 0x0000, 0x171C, 0x0000, 0x134E, 0x0080, 0x0041, 0x005E, 0x7C01, 0x7DE9, 0xFFFF, 0x0900, 0x1000, },
+	{0x10C3, 0x0000, 0x128B, 0x0080, 0x1555, 0x0000, 0x165A, 0x0080, 0x0083, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x1000, 0x9000, },
+	{0x1104, 0x0000, 0x138E, 0x0000, 0x171C, 0x0000, 0x16DC, 0x0080, 0x0082, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x1100, 0x1800, },
+	{0x10C3, 0x0000, 0x128B, 0x0080, 0x1555, 0x0000, 0x169A, 0x0000, 0x0083, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x1000, 0x9000, },
+	{0x1104, 0x0000, 0x138E, 0x0000, 0x171C, 0x0000, 0x11C7, 0x0000, 0x1041, 0x01BC, 0x7C01, 0x7DE9, 0xFFFF, 0x9900, 0x8100, },
+	{0x10C3, 0x0000, 0x128B, 0x0080, 0x1555, 0x0000, 0x169B, 0x0080, 0x0083, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x1800, 0x0100, },
+	{0x1104, 0x0000, 0x138E, 0x0000, 0x171C, 0x0000, 0x171D, 0x0080, 0x0082, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x1100, 0x1800, },
+	{0x10C3, 0x0000, 0x128B, 0x0080, 0x1555, 0x0000, 0x16DB, 0x0000, 0x0083, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x1800, 0x0100, },
+	{0x10C3, 0x0000, 0x128B, 0x0080, 0x1555, 0x0000, 0x1146, 0x0080, 0x1041, 0x0184, 0x7C01, 0x7DE9, 0xFFFF, 0x9900, 0x8100, },
+	{0x10C3, 0x0000, 0x128B, 0x0080, 0x1555, 0x0000, 0x171C, 0x0000, 0x0083, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x1100, 0x1800, },
+	{0x10C3, 0x0000, 0x128B, 0x0080, 0x1555, 0x0000, 0x1451, 0x0000, 0x0042, 0x0013, 0x7C01, 0x7DE9, 0xFFFF, 0x0900, 0x1000, },
+	{0x10C3, 0x0000, 0x128B, 0x0080, 0x1555, 0x0000, 0x171D, 0x0080, 0x0083, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x1100, 0x1800, },
+	{0x10C3, 0x0000, 0x128B, 0x0080, 0x1555, 0x0000, 0x175D, 0x0000, 0x0083, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x0800, 0x1000, },
+	{0x10C3, 0x0000, 0x128B, 0x0080, 0x1555, 0x0000, 0x1452, 0x0080, 0x0042, 0x0013, 0x7C01, 0x7DE9, 0xFFFF, 0x0900, 0x1000, },
+	{0x10C3, 0x0000, 0x128B, 0x0080, 0x1555, 0x0000, 0x15D8, 0x0080, 0x0082, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x1900, 0x0100, },
+	{0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x1104, 0x0000, 0x1041, 0x01E8, 0x5801, 0x59E9, 0xFFFF, 0x9900, 0x0900, },
+	{0x10C3, 0x0000, 0x128B, 0x0080, 0x1555, 0x0000, 0x179F, 0x0080, 0x0083, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x0800, 0x1000, },
+	{0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x1515, 0x0080, 0x0083, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x0900, 0x1000, },
+	{0x10C3, 0x0000, 0x128B, 0x0080, 0x1555, 0x0000, 0x17DF, 0x0000, 0x0083, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x1000, 0x8800, },
+	{0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x1659, 0x0000, 0x00C3, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x1000, 0x9000, },
+	{0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x1555, 0x0000, 0x0083, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x0800, 0x8000, },
+	{0x10C3, 0x0000, 0x128B, 0x0080, 0x1555, 0x0000, 0x14D3, 0x0000, 0x0042, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x0900, 0x1000, },
+	{0x10C3, 0x0000, 0x128B, 0x0080, 0x1555, 0x0000, 0x1820, 0x0000, 0x0083, 0x00FA, 0x7DE9, 0x7DE8, 0xFFFF, 0x0900, 0x9000, },
+	{0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x1556, 0x0080, 0x0083, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x0800, 0x8000, },
+	{0x10C3, 0x0000, 0x128B, 0x0080, 0x1555, 0x0000, 0x1187, 0x0080, 0x1041, 0x01EE, 0x7C01, 0x7DE9, 0xFFFF, 0x9900, 0x8100, },
+	{0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x1452, 0x0080, 0x0082, 0x0013, 0x7C01, 0x7DE9, 0xFFFF, 0x0900, 0x1000, },
+	{0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x169B, 0x0080, 0x00C3, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x1800, 0x0100, },
+	{0x10C3, 0x0000, 0x128B, 0x0080, 0x1555, 0x0000, 0x1514, 0x0000, 0x0042, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x0900, 0x1000, },
+	{0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x17E0, 0x0080, 0x00C4, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x0900, 0x9000, },
+	{0x10C3, 0x0000, 0x128B, 0x0080, 0x1555, 0x0000, 0x1515, 0x0080, 0x0042, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x0900, 0x1000, },
+	{0x10C3, 0x0000, 0x128B, 0x0080, 0x1555, 0x0000, 0x16DC, 0x0080, 0x0082, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x1100, 0x1800, },
+	{0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x1493, 0x0080, 0x0082, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x0900, 0x1000, },
+	{0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x171C, 0x0000, 0x00C3, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x1100, 0x1800, },
+	{0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x15D8, 0x0080, 0x0083, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x1900, 0x0100, },
+	{0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x171D, 0x0080, 0x00C3, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x1100, 0x1800, },
+	{0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x1618, 0x0000, 0x0083, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x1900, 0x0100, },
+	{0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x175D, 0x0000, 0x00C3, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x0800, 0x1000, },
+	{0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x14D4, 0x0080, 0x0082, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x0900, 0x1000, },
+	{0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x1619, 0x0080, 0x0083, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x1000, 0x9000, },
+	{0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x179E, 0x0000, 0x00C3, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x0800, 0x1000, },
+	{0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x179F, 0x0080, 0x00C3, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x0800, 0x1000, },
+	{0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x1515, 0x0080, 0x0082, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x0900, 0x1000, },
+	{0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x13D0, 0x0080, 0x0042, 0x002C, 0x7C01, 0x7DE9, 0xFFFF, 0x0900, 0x1000, },
+	{0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x169A, 0x0000, 0x0083, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x1000, 0x9000, },
+	{0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x128B, 0x0080, 0x0041, 0x00DB, 0x7C01, 0x7DE9, 0xFFFF, 0x9000, 0x0100, },
+	{0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x169B, 0x0080, 0x0083, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x1800, 0x0100, },
+	{0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x1820, 0x0000, 0x00C3, 0x00FA, 0x7DE9, 0x7DE8, 0xFFFF, 0x0900, 0x9000, },
+	{0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x1556, 0x0080, 0x0082, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x0800, 0x8000, },
+	{0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x16DB, 0x0000, 0x0083, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x1800, 0x0100, },
+	{0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x1411, 0x0080, 0x0042, 0x002C, 0x7C01, 0x7DE9, 0xFFFF, 0x0900, 0x1000, },
+	{0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x171C, 0x0000, 0x0083, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x1100, 0x1800, },
+	{0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x1597, 0x0080, 0x0082, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x0800, 0x8000, },
+	{0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x1451, 0x0000, 0x0042, 0x0013, 0x7C01, 0x7DE9, 0xFFFF, 0x0900, 0x1000, },
+	{0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x171D, 0x0080, 0x0083, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x1100, 0x1800, },
+	{0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x12CC, 0x0080, 0x0041, 0x00A9, 0x7C01, 0x7DE9, 0xFFFF, 0x1100, 0x9000, },
+	{0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x175D, 0x0000, 0x0083, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x0800, 0x1000, },
+	{0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x1452, 0x0080, 0x0042, 0x0013, 0x7C01, 0x7DE9, 0xFFFF, 0x0900, 0x1000, },
+	{0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x15D8, 0x0080, 0x0082, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x1900, 0x0100, },
+	{0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x175E, 0x0080, 0x0083, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x0800, 0x1000, },
+	{0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x1492, 0x0000, 0x0042, 0x0013, 0x7C01, 0x7DE9, 0xFFFF, 0x0900, 0x1000, },
+	{0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x179F, 0x0080, 0x0083, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x0800, 0x1000, },
+	{0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x1619, 0x0080, 0x0082, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x1000, 0x9000, },
+	{0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x1493, 0x0080, 0x0042, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x0900, 0x1000, },
+	{0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x17DF, 0x0000, 0x0083, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x1000, 0x8800, },
+	{0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x130D, 0x0080, 0x0041, 0x0090, 0x7C01, 0x7DE9, 0xFFFF, 0x1100, 0x9000, },
+	{0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x17E0, 0x0080, 0x0083, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x0900, 0x9000, },
+	{0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x14D3, 0x0000, 0x0042, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x0900, 0x1000, },
+	{0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x165A, 0x0080, 0x0082, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x1000, 0x9000, },
+	{0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x1820, 0x0000, 0x0083, 0x00FA, 0x7DE9, 0x7DE8, 0xFFFF, 0x0900, 0x9000, },
+	{0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x14D4, 0x0080, 0x0042, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x0900, 0x1000, },
+	{0x1082, 0x0000, 0x11C7, 0x0000, 0x138E, 0x0000, 0x169B, 0x0080, 0x0082, 0x00FA, 0x7C01, 0x7DE9, 0xFFFF, 0x1800, 0x0100, },
+};
+
+static const uint16_t cmt_vals_in[][13] = {
+	{0x1082, 0x0000, 0x5104, 0x0000, 0x11C7, 0x0000, 0x1041, 0x02BC, 0x7C01, 0xFFE9, 0x9900, 0x9908, 0x8100},
+	{0x1104, 0x0000, 0x9208, 0x0000, 0x138E, 0x0000, 0x1041, 0x015E, 0x7C01, 0xFFE9, 0x0100, 0x0908, 0x1000},
+};
+
+static const uint32_t cmt_addrs_out[][15] = {
+	{0x420, 0x424, 0x428, 0x42C, 0x430, 0x434, 0x450, 0x454, 0x458, 0x460, 0x464, 0x468, 0x4A0, 0x538, 0x53C},
+	{0x620, 0x624, 0x628, 0x62C, 0x630, 0x634, 0x650, 0x654, 0x658, 0x660, 0x664, 0x668, 0x6A0, 0x738, 0x73C},
+};
+
+static const uint32_t cmt_addrs_in[][13] = {
+	{0x020, 0x024, 0x028, 0x02C, 0x050, 0x054, 0x058, 0x060, 0x064, 0x068, 0x0A0, 0x138, 0x13C},
+	{0x220, 0x224, 0x228, 0x22C, 0x250, 0x254, 0x258, 0x260, 0x264, 0x268, 0x2A0, 0x338, 0x33C},
+};
+
+static const uint32_t cmt_freq[] = {
+	25000, 25510, 26020, 26530, 26983, 27551, 28000, 28570,
+	29046, 29522, 30000, 30476, 30952, 31546, 32000, 32539,
+	33035, 33571, 33928, 34522, 35000, 35428, 36000, 36571,
+	36904, 37500, 38093, 38571, 39047, 39453, 40000, 40476,
+	40952, 41494, 41964, 42857, 43535, 44047, 44444, 45000,
+	45535, 46029, 46428, 46823, 47617, 48214, 48571, 49107,
+	49523, 50000, 50476, 50892, 51428, 52380, 53333, 53967,
+	54285, 55238, 55555, 55952, 57142, 58095, 58571, 59047,
+	59521, 60000, 60316, 60952, 61428, 61904, 62500, 63092,
+	63491, 64282, 65078, 65476, 66071, 66664, 67142, 67854,
+	68571, 69044, 69642, 70000, 71425, 72616, 73214, 73808,
+	74285, 75000, 75714, 76187, 76785, 77142, 78570, 80000,
+	80357, 80951, 81428, 82142, 82857, 83332, 83928, 84285,
+	85713, 87142, 87500, 88094, 88571, 89285, 90000, 90475,
+	91071, 91428, 92856, 94642,
+};
+
+
+static size_t freq_srch(u32 key, const u32 *array, size_t size)
+{
+	int l = 0;
+	int r = size - 1;
+	int m;
+
+	while (l <= r) {
+		m = (l + r) / 2;
+		if (array[m] < key)
+			l = m + 1;
+		else if (array[m] > key)
+			r = m - 1;
+		else
+			return m;
+	}
+
+	if (r < 0 || l > size - 1)
+		return m;
+	else
+		return (abs(key - array[l]) < abs(key - array[r])) ? l : r;
+}
+
+
+u32 mgb4_cmt_set_vout(struct mgb4_vout_dev *voutdev, unsigned int freq)
+{
+	const uint16_t *reg_set;
+	const uint32_t *addr;
+	u32 config;
+	size_t i, index;
+
+	index = freq_srch(freq, cmt_freq, ARRAY_SIZE(cmt_freq));
+	addr = cmt_addrs_out[voutdev->config->id];
+	reg_set = cmt_vals_out[index];
+
+	config = mgb4_read_reg(&voutdev->mgbdev->video, voutdev->config->regs.config);
+
+	mgb4_write_reg(&voutdev->mgbdev->video, voutdev->config->regs.config,
+	  0x1 | (config & ~0x3));
+
+	for (i = 0; i < ARRAY_SIZE(cmt_addrs_out[0]); i++)
+		mgb4_write_reg(&voutdev->mgbdev->cmt, addr[i], reg_set[i]);
+
+	mgb4_mask_reg(&voutdev->mgbdev->video, voutdev->config->regs.config,
+	  0x100, 0x100);
+	mgb4_mask_reg(&voutdev->mgbdev->video, voutdev->config->regs.config,
+	  0x100, 0x0);
+
+	mgb4_write_reg(&voutdev->mgbdev->video, voutdev->config->regs.config,
+	  config & ~0x1);
+
+	return cmt_freq[index];
+}
+
+void mgb4_cmt_set_vin(struct mgb4_vin_dev *vindev, unsigned int freq_range)
+{
+	const uint16_t *reg_set;
+	const uint32_t *addr;
+	u32 config;
+	size_t i;
+
+	addr = cmt_addrs_in[vindev->config->id];
+	reg_set = cmt_vals_in[freq_range];
+
+	config = mgb4_read_reg(&vindev->mgbdev->video, vindev->config->regs.config);
+
+	mgb4_write_reg(&vindev->mgbdev->video, vindev->config->regs.config,
+	  0x1 | (config & ~0x3));
+
+	for (i = 0; i < ARRAY_SIZE(cmt_addrs_in[0]); i++)
+		mgb4_write_reg(&vindev->mgbdev->cmt, addr[i], reg_set[i]);
+
+	mgb4_mask_reg(&vindev->mgbdev->video, vindev->config->regs.config,
+	  0x1000, 0x1000);
+	mgb4_mask_reg(&vindev->mgbdev->video, vindev->config->regs.config,
+	  0x1000, 0x0);
+
+	mgb4_write_reg(&vindev->mgbdev->video, vindev->config->regs.config,
+	  config & ~0x1);
+}
diff --git a/drivers/media/pci/mgb4/mgb4_cmt.h b/drivers/media/pci/mgb4/mgb4_cmt.h
new file mode 100644
index 000000000000..353966654c95
--- /dev/null
+++ b/drivers/media/pci/mgb4/mgb4_cmt.h
@@ -0,0 +1,16 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) 2021-2022 Digiteq Automotive
+ *     author: Martin Tuma <martin.tuma@digiteqautomotive.com>
+ */
+
+#ifndef __MGB4_CMT_H__
+#define __MGB4_CMT_H__
+
+#include "mgb4_vout.h"
+#include "mgb4_vin.h"
+
+u32 mgb4_cmt_set_vout(struct mgb4_vout_dev *voutdev, unsigned int freq);
+void mgb4_cmt_set_vin(struct mgb4_vin_dev *vindev, unsigned int freq_range);
+
+#endif
diff --git a/drivers/media/pci/mgb4/mgb4_core.c b/drivers/media/pci/mgb4/mgb4_core.c
new file mode 100644
index 000000000000..1bfc2712401c
--- /dev/null
+++ b/drivers/media/pci/mgb4/mgb4_core.c
@@ -0,0 +1,554 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * This is the driver for the MGB4 video grabber card by Digiteq Automotive.
+ *
+ * Copyright (C) 2021-2022 Digiteq Automotive
+ *     author: Martin Tuma <martin.tuma@digiteqautomotive.com>
+ */
+
+#include <linux/types.h>
+#include <linux/version.h>
+#include <linux/module.h>
+#include <linux/pci.h>
+#include <linux/platform_device.h>
+#include <linux/clk.h>
+#include <linux/clk-provider.h>
+#include <linux/clkdev.h>
+#include <linux/i2c.h>
+#include <linux/delay.h>
+#include <linux/spi/xilinx_spi.h>
+#include <linux/mtd/mtd.h>
+#include "mgb4_i2c.h"
+#include "mgb4_sysfs.h"
+#include "mgb4_vout.h"
+#include "mgb4_vin.h"
+#include "mgb4_trigger.h"
+#include "mgb4_core.h"
+
+
+#define MGB4_USER_IRQS 16
+
+static int flashid;
+
+static struct xspi_platform_data spi_platform_data = {
+	.num_chipselect = 1,
+	.bits_per_word = 8
+};
+
+static const struct i2c_board_info extender_info = {
+	I2C_BOARD_INFO("extender", 0x21)
+};
+
+static int match_i2c_adap(struct device *dev, void *data)
+{
+	return (i2c_verify_adapter(dev) != NULL);
+}
+
+static struct i2c_adapter *get_i2c_adap(struct platform_device *pdev)
+{
+	struct device *dev;
+	int i;
+
+	for (i = 0; i < 10; i++) {
+		msleep(100);
+		mutex_lock(&pdev->dev.mutex);
+		dev = device_find_child(&pdev->dev, NULL, match_i2c_adap);
+		mutex_unlock(&pdev->dev.mutex);
+		if (dev)
+			return to_i2c_adapter(dev);
+	}
+
+	return NULL;
+}
+
+static int match_spi_adap(struct device *dev, void *data)
+{
+	return (to_spi_device(dev) != NULL);
+}
+
+static struct spi_master *get_spi_adap(struct platform_device *pdev)
+{
+	struct device *dev;
+	int i;
+
+	for (i = 0; i < 10; i++) {
+		msleep(100);
+		mutex_lock(&pdev->dev.mutex);
+		dev = device_find_child(&pdev->dev, NULL, match_spi_adap);
+		mutex_unlock(&pdev->dev.mutex);
+		if (dev)
+			return container_of(dev, struct spi_master, dev);
+	}
+
+	return NULL;
+}
+
+static int init_spi(struct mgb4_dev *mgbdev)
+{
+	struct resource spi_resources[] = {
+		{
+			.start	= 0x400,
+			.end	= 0x47f,
+			.flags	= IORESOURCE_MEM,
+			.name	= "io-memory",
+		},
+		{
+			.start	= 14,
+			.end	= 14,
+			.flags	= IORESOURCE_IRQ,
+			.name	= "irq",
+		},
+	};
+	struct spi_board_info spi_info = {
+		.max_speed_hz = 10000000,
+		.modalias = "m25p80",
+		.chip_select = 0,
+		.mode = SPI_MODE_3,
+	};
+	struct pci_dev *pdev = mgbdev->xdma.pdev;
+	struct device *dev = &pdev->dev;
+	struct spi_master *master;
+	struct spi_device *spi_dev;
+	int rv, id;
+	resource_size_t mapbase = pci_resource_start(pdev, MGB4_MGB4_BAR_ID);
+
+	spi_resources[0].parent = &pdev->resource[0];
+	spi_resources[0].start += mapbase;
+	spi_resources[0].end += mapbase;
+	spi_resources[1].start += MGB4_IRQ_BASE(pdev);
+	spi_resources[1].end += MGB4_IRQ_BASE(pdev);
+
+	xdma_irq_enable(&mgbdev->xdma, 1U<<14);
+
+	id = pci_dev_id(pdev);
+	mgbdev->spi_pdev = platform_device_register_resndata(dev, "xilinx_spi",
+		id, spi_resources, ARRAY_SIZE(spi_resources), &spi_platform_data,
+		sizeof(spi_platform_data));
+	if (IS_ERR(mgbdev->spi_pdev)) {
+		dev_err(dev, "failed to register SPI device\n");
+		return PTR_ERR(mgbdev->spi_pdev);
+	}
+
+	master = get_spi_adap(mgbdev->spi_pdev);
+	if (!master) {
+		dev_err(dev, "failed to get SPI adapter\n");
+		rv = -EINVAL;
+		goto err_pdev;
+	}
+
+	snprintf(mgbdev->fw_part_name, sizeof(mgbdev->fw_part_name), "mgb4-fw.%d",
+	  flashid);
+	mgbdev->partitions[0].name = mgbdev->fw_part_name;
+	mgbdev->partitions[0].size = 0x400000;
+	mgbdev->partitions[0].offset = 0x400000;
+	mgbdev->partitions[0].mask_flags = 0;
+
+	snprintf(mgbdev->data_part_name, sizeof(mgbdev->data_part_name),
+	  "mgb4-data.%d", flashid);
+	mgbdev->partitions[1].name = mgbdev->data_part_name;
+	mgbdev->partitions[1].size = 0x10000;
+	mgbdev->partitions[1].offset = 0xFF0000;
+	mgbdev->partitions[1].mask_flags = MTD_CAP_NORFLASH;
+
+	snprintf(mgbdev->flash_name, sizeof(mgbdev->flash_name), "mgb4-flash.%d",
+	  flashid);
+	mgbdev->flash_data.name = mgbdev->flash_name;
+	mgbdev->flash_data.parts = mgbdev->partitions;
+	mgbdev->flash_data.nr_parts = ARRAY_SIZE(mgbdev->partitions);
+	mgbdev->flash_data.type = "spi-nor";
+
+	spi_info.platform_data = &(mgbdev->flash_data);
+
+	spi_dev = spi_new_device(master, &spi_info);
+	put_device(&master->dev);
+	if (!spi_dev) {
+		dev_err(dev, "failed to create MTD device\n");
+		rv = -EINVAL;
+		goto err_pdev;
+	}
+
+	return 0;
+
+err_pdev:
+	platform_device_unregister(mgbdev->spi_pdev);
+
+	return rv;
+}
+
+static void free_spi(struct mgb4_dev *mgbdev)
+{
+	platform_device_unregister(mgbdev->spi_pdev);
+}
+
+static int init_i2c(struct mgb4_dev *mgbdev)
+{
+	struct resource i2c_resources[] = {
+		{
+			.start	= 0x200,
+			.end	= 0x3ff,
+			.flags	= IORESOURCE_MEM,
+			.name	= "io-memory",
+		},
+		{
+			.start	= 15,
+			.end	= 15,
+			.flags	= IORESOURCE_IRQ,
+			.name	= "irq",
+		},
+	};
+	struct pci_dev *pdev = mgbdev->xdma.pdev;
+	struct device *dev = &pdev->dev;
+	char clk_name[16];
+	int rv, id;
+	resource_size_t mapbase = pci_resource_start(pdev, MGB4_MGB4_BAR_ID);
+
+	i2c_resources[0].parent = &pdev->resource[0];
+	i2c_resources[0].start += mapbase;
+	i2c_resources[0].end += mapbase;
+	i2c_resources[1].start += MGB4_IRQ_BASE(pdev);
+	i2c_resources[1].end += MGB4_IRQ_BASE(pdev);
+
+	id = pci_dev_id(pdev);
+
+	// create dummy clock required by the xiic-i2c adapter
+	snprintf(clk_name, sizeof(clk_name), "xiic-i2c.%d", id);
+	mgbdev->i2c_clk = clk_hw_register_fixed_rate(NULL, clk_name, NULL, 0, 125000000);
+	if (IS_ERR(mgbdev->i2c_clk)) {
+		dev_err(dev, "failed to register I2C clock\n");
+		return PTR_ERR(mgbdev->i2c_clk);
+	}
+	mgbdev->i2c_cl = clkdev_hw_create(mgbdev->i2c_clk, NULL, "xiic-i2c.%d", id);
+	if (!mgbdev->i2c_cl) {
+		dev_err(dev, "failed to register I2C clockdev\n");
+		rv = -ENOMEM;
+		goto err_clk;
+	}
+
+	xdma_irq_enable(&mgbdev->xdma, 1U<<15);
+
+	mgbdev->i2c_pdev = platform_device_register_resndata(dev, "xiic-i2c",
+		id, i2c_resources, ARRAY_SIZE(i2c_resources), NULL, 0);
+	if (IS_ERR(mgbdev->i2c_pdev)) {
+		dev_err(dev, "failed to register I2C device\n");
+		rv = PTR_ERR(mgbdev->i2c_pdev);
+		goto err_clkdev;
+	}
+
+	mgbdev->i2c_adap = get_i2c_adap(mgbdev->i2c_pdev);
+	if (!mgbdev->i2c_adap) {
+		dev_err(dev, "failed to get I2C adapter\n");
+		rv = -EINVAL;
+		goto err_pdev;
+	}
+
+	mutex_init(&mgbdev->i2c_lock);
+
+	return 0;
+
+err_pdev:
+	platform_device_unregister(mgbdev->i2c_pdev);
+err_clkdev:
+	clkdev_drop(mgbdev->i2c_cl);
+err_clk:
+	clk_hw_unregister(mgbdev->i2c_clk);
+
+	return rv;
+}
+
+static void free_i2c(struct mgb4_dev *mgbdev)
+{
+	put_device(&mgbdev->i2c_adap->dev);
+	platform_device_unregister(mgbdev->i2c_pdev);
+	clkdev_drop(mgbdev->i2c_cl);
+	clk_hw_unregister(mgbdev->i2c_clk);
+}
+
+static int init_sysfs(struct pci_dev *pdev)
+{
+	struct device_attribute **attr, **eattr;
+	int rv;
+
+	for (attr = mgb4_pci_attrs; *attr; attr++) {
+		rv = device_create_file(&pdev->dev, *attr);
+		if (rv < 0)
+			goto err_file;
+	}
+
+	return 0;
+
+err_file:
+	for (eattr = mgb4_pci_attrs; eattr != attr; eattr++)
+		device_remove_file(&pdev->dev, *eattr);
+
+	return rv;
+}
+
+static void free_sysfs(struct pci_dev *pdev)
+{
+	struct device_attribute **attr;
+
+	for (attr = mgb4_pci_attrs; *attr; attr++)
+		device_remove_file(&pdev->dev, *attr);
+}
+
+static int get_serial_number(struct mgb4_dev *mgbdev)
+{
+	struct device *dev = &mgbdev->xdma.pdev->dev;
+	struct mtd_info *mtd;
+	size_t rs;
+	int rv;
+
+	mgbdev->serial_number = 0;
+
+	mtd = get_mtd_device_nm(mgbdev->data_part_name);
+	if (IS_ERR(mtd)) {
+		dev_warn(dev, "failed to get data MTD device\n");
+		return -ENOENT;
+	}
+	rv = mtd_read(mtd, 0, sizeof(mgbdev->serial_number), &rs,
+	  (u_char *)&mgbdev->serial_number);
+	put_mtd_device(mtd);
+	if (rv < 0 || rs != sizeof(mgbdev->serial_number)) {
+		dev_warn(dev, "error reading MTD device\n");
+		return -EIO;
+	}
+
+	return 0;
+}
+
+static int get_module_version(struct mgb4_dev *mgbdev)
+{
+	struct device *dev = &mgbdev->xdma.pdev->dev;
+	struct mgb4_i2c_client extender;
+	s32 version;
+	u32 fw_version;
+	int rv;
+
+	rv = mgb4_i2c_init(&extender, mgbdev->i2c_adap, &extender_info, 8);
+	if (rv < 0) {
+		dev_err(dev, "failed to create extender I2C device\n");
+		return rv;
+	}
+	version = mgb4_i2c_read_byte(&extender, 0x00);
+	mgb4_i2c_free(&extender);
+	if (version < 0) {
+		dev_err(dev, "error reading module version\n");
+		return -EIO;
+	}
+
+	mgbdev->module_version = ~((u32)version) & 0xff;
+	if (!(MGB4_IS_FPDL3(mgbdev) || MGB4_IS_GMSL(mgbdev))) {
+		dev_err(dev, "unknown module type\n");
+		return -EINVAL;
+	}
+	fw_version = mgb4_read_reg(&mgbdev->video, 0xC4);
+	if (fw_version >> 24 != mgbdev->module_version >> 4) {
+		dev_err(dev, "module/firmware type mismatch\n");
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int map_regs(struct pci_dev *pdev, struct resource *res,
+		    struct mgb4_regs *regs)
+{
+	int rv;
+	resource_size_t mapbase = pci_resource_start(pdev, MGB4_MGB4_BAR_ID);
+
+	res->start += mapbase;
+	res->end += mapbase;
+
+	rv = mgb4_regs_map(res, regs);
+	if (rv < 0) {
+		dev_err(&pdev->dev, "failed to map %s registers\n", res->name);
+		return rv;
+	}
+
+	return 0;
+}
+
+static int mgb4_probe(struct pci_dev *pdev, const struct pci_device_id *id)
+{
+	int i, rv;
+	struct mgb4_dev *mgbdev;
+	struct resource video = {
+		.start	= 0x0,
+		.end	= 0x100,
+		.flags	= IORESOURCE_MEM,
+		.name	= "mgb4-video",
+	};
+	struct resource cmt = {
+		.start	= 0x1000,
+		.end	= 0x1800,
+		.flags	= IORESOURCE_MEM,
+		.name	= "mgb4-cmt",
+	};
+	int irqs = MGB4_VIN_DEVICES + MGB4_VOUT_DEVICES + MGB4_USER_IRQS;
+
+
+	mgbdev = kzalloc(sizeof(*mgbdev), GFP_KERNEL);
+	if (!mgbdev)
+		return -ENOMEM;
+
+	pci_set_drvdata(pdev, mgbdev);
+
+	/* PCIe related stuff */
+	rv = pci_enable_device(pdev);
+	if (rv) {
+		dev_err(&pdev->dev, "error enabling PCI device\n");
+		goto err_mgbdev;
+	}
+
+	rv = pcie_capability_set_word(pdev, PCI_EXP_DEVCTL, PCI_EXP_DEVCTL_RELAX_EN);
+	if (rv)
+		dev_warn(&pdev->dev, "error enabling PCIe relaxed ordering\n");
+	rv = pcie_capability_set_word(pdev, PCI_EXP_DEVCTL, PCI_EXP_DEVCTL_EXT_TAG);
+	if (rv)
+		dev_warn(&pdev->dev, "error enabling PCIe extended tag field\n");
+	rv = pcie_set_readrq(pdev, 512);
+	if (rv)
+		dev_warn(&pdev->dev, "error setting PCIe max. memory read size\n");
+	pci_set_master(pdev);
+
+	rv = pci_alloc_irq_vectors(pdev, irqs, irqs, PCI_IRQ_MSIX);
+	if (rv < 0) {
+		dev_err(&pdev->dev, "error allocating MSI-X IRQs\n");
+		goto err_enable_pci;
+	}
+
+	/* DMA + IRQ engine */
+	mgbdev->xdma.pdev = pdev;
+	mgbdev->xdma.config_bar_id = MGB4_XDMA_BAR_ID;
+	mgbdev->xdma.user_irq_max = MGB4_USER_IRQS;
+	mgbdev->xdma.c2h_channel_max = MGB4_VIN_DEVICES;
+	mgbdev->xdma.h2c_channel_max = MGB4_VOUT_DEVICES;
+
+	rv = xdma_probe(&mgbdev->xdma);
+	if (rv) {
+		dev_err(&pdev->dev, "failed to initialize XDMA device\n");
+		goto err_alloc_irq;
+	}
+
+	/* mgb4 video registers */
+	rv = map_regs(pdev, &video, &mgbdev->video);
+	if (rv < 0)
+		goto err_xdev;
+	/* mgb4 cmt registers */
+	rv = map_regs(pdev, &cmt, &mgbdev->cmt);
+	if (rv < 0)
+		goto err_video_regs;
+
+	/* SPI FLASH */
+	rv = init_spi(mgbdev);
+	if (rv < 0)
+		goto err_cmt_regs;
+
+	/* I2C controller */
+	rv = init_i2c(mgbdev);
+	if (rv < 0)
+		goto err_spi;
+
+	/* PCI card related sysfs attributes */
+	rv = init_sysfs(pdev);
+	if (rv < 0)
+		goto err_i2c;
+
+	/* Get card serial number. On systems without MTD flash support we may
+	 * get an error thus ignore the return value. An invalid serial number
+	 * should not break anything...
+	 */
+	if (get_serial_number(mgbdev) < 0)
+		dev_warn(&pdev->dev, "error reading card serial number\n");
+
+	/* Get module type. If no valid module is found, skip the video device
+	 * creation part but do not exit with error to allow flashing the card.
+	 */
+	rv = get_module_version(mgbdev);
+	if (rv < 0)
+		goto exit;
+
+	/* Video input v4l2 devices */
+	for (i = 0; i < MGB4_VIN_DEVICES; i++)
+		mgbdev->vin[i] = mgb4_vin_create(mgbdev, i);
+
+	/* Video output v4l2 devices */
+	for (i = 0; i < MGB4_VOUT_DEVICES; i++)
+		mgbdev->vout[i] = mgb4_vout_create(mgbdev, i);
+
+	/* Triggers */
+	mgbdev->indio_dev = mgb4_trigger_create(mgbdev);
+
+exit:
+	flashid++;
+
+	return 0;
+
+err_i2c:
+	free_i2c(mgbdev);
+err_spi:
+	free_spi(mgbdev);
+err_cmt_regs:
+	mgb4_regs_free(&mgbdev->cmt);
+err_video_regs:
+	mgb4_regs_free(&mgbdev->video);
+err_xdev:
+	xdma_remove(&mgbdev->xdma);
+err_alloc_irq:
+	pci_disable_msix(pdev);
+err_enable_pci:
+	pci_disable_device(pdev);
+err_mgbdev:
+	kfree(mgbdev);
+
+	return rv;
+}
+
+static void mgb4_remove(struct pci_dev *pdev)
+{
+	struct mgb4_dev *mgbdev = pci_get_drvdata(pdev);
+	int i;
+
+	if (mgbdev->indio_dev)
+		mgb4_trigger_free(mgbdev->indio_dev);
+
+	for (i = 0; i < MGB4_VOUT_DEVICES; i++)
+		if (mgbdev->vout[i])
+			mgb4_vout_free(mgbdev->vout[i]);
+	for (i = 0; i < MGB4_VIN_DEVICES; i++)
+		if (mgbdev->vin[i])
+			mgb4_vin_free(mgbdev->vin[i]);
+
+	free_sysfs(mgbdev->xdma.pdev);
+	free_spi(mgbdev);
+	free_i2c(mgbdev);
+	mgb4_regs_free(&mgbdev->video);
+	mgb4_regs_free(&mgbdev->cmt);
+
+	xdma_remove(&mgbdev->xdma);
+
+	pci_disable_msix(mgbdev->xdma.pdev);
+	pci_disable_device(mgbdev->xdma.pdev);
+
+	kfree(mgbdev);
+}
+
+static const struct pci_device_id mgb4_pci_ids[] = {
+	{ PCI_DEVICE(0x1ed8, 0x0101), },
+	{ 0, }
+};
+MODULE_DEVICE_TABLE(pci, mgb4_pci_ids);
+
+static struct pci_driver mgb4_pci_driver = {
+	.name = KBUILD_MODNAME,
+	.id_table = mgb4_pci_ids,
+	.probe = mgb4_probe,
+	.remove = mgb4_remove,
+};
+
+module_pci_driver(mgb4_pci_driver);
+
+MODULE_AUTHOR("Digiteq Automotive s.r.o.");
+MODULE_DESCRIPTION("Digiteq Automotive MGB4 Driver");
+MODULE_LICENSE("GPL");
+MODULE_SOFTDEP("pre: platform:xiic-i2c platform:xilinx_spi spi-nor");
diff --git a/drivers/media/pci/mgb4/mgb4_core.h b/drivers/media/pci/mgb4/mgb4_core.h
new file mode 100644
index 000000000000..e56dade71bbb
--- /dev/null
+++ b/drivers/media/pci/mgb4/mgb4_core.h
@@ -0,0 +1,58 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) 2021-2022 Digiteq Automotive
+ *     author: Martin Tuma <martin.tuma@digiteqautomotive.com>
+ */
+
+#ifndef __MGB4_CORE_H__
+#define __MGB4_CORE_H__
+
+#include <linux/spi/flash.h>
+#include <linux/mtd/partitions.h>
+#include <linux/mutex.h>
+#include <linux/dma/xilinx_xdma.h>
+#include "mgb4_regs.h"
+
+#define MGB4_VIN_DEVICES  2
+#define MGB4_VOUT_DEVICES 2
+
+#define MGB4_MGB4_BAR_ID  0
+#define MGB4_XDMA_BAR_ID  1
+
+#define MGB4_IRQ_BASE(pdev) \
+	(pci_irq_vector((pdev), MGB4_MGB4_BAR_ID) \
+	+ MGB4_VIN_DEVICES + MGB4_VOUT_DEVICES)
+
+#define MGB4_IS_GMSL(mgbdev) \
+	((mgbdev)->module_version >> 4 == 2)
+#define MGB4_IS_FPDL3(mgbdev) \
+	((mgbdev)->module_version >> 4 == 1)
+
+struct mgb4_dev {
+	struct xdma_core xdma;
+	struct mgb4_vin_dev *vin[MGB4_VIN_DEVICES];
+	struct mgb4_vout_dev *vout[MGB4_VOUT_DEVICES];
+
+	struct mgb4_regs video;
+	struct mgb4_regs cmt;
+
+	struct clk_hw *i2c_clk;
+	struct clk_lookup *i2c_cl;
+	struct platform_device *i2c_pdev;
+	struct i2c_adapter *i2c_adap;
+	struct mutex i2c_lock;
+
+	struct platform_device *spi_pdev;
+	struct flash_platform_data flash_data;
+	char flash_name[16];
+	struct mtd_partition partitions[2];
+	char fw_part_name[16];
+	char data_part_name[16];
+
+	struct iio_dev *indio_dev;
+
+	u8 module_version;
+	u32 serial_number;
+};
+
+#endif
diff --git a/drivers/media/pci/mgb4/mgb4_i2c.c b/drivers/media/pci/mgb4/mgb4_i2c.c
new file mode 100644
index 000000000000..536dc34b7f82
--- /dev/null
+++ b/drivers/media/pci/mgb4/mgb4_i2c.c
@@ -0,0 +1,139 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2021-2022 Digiteq Automotive
+ *     author: Martin Tuma <martin.tuma@digiteqautomotive.com>
+ */
+
+#include <linux/version.h>
+#include "mgb4_i2c.h"
+
+static int read_r16(struct i2c_client *client, u16 reg, u8 *val, int len)
+{
+	int ret;
+	u8 buf[2];
+	struct i2c_msg msg[2] = {
+		{
+			.addr = client->addr,
+			.flags = 0,
+			.len = 2,
+			.buf = buf,
+		}, {
+			.addr = client->addr,
+			.flags = I2C_M_RD,
+			.len = len,
+			.buf = val,
+		}
+	};
+
+	buf[0] = (reg >> 8) & 0xff;
+	buf[1] = (reg >> 0) & 0xff;
+
+	ret = i2c_transfer(client->adapter, msg, 2);
+	if (ret < 0)
+		return ret;
+	else if (ret != 2)
+		return -EREMOTEIO;
+	else
+		return 0;
+}
+
+static int write_r16(struct i2c_client *client, u16 reg, const u8 *val, int len)
+{
+	int ret;
+	u8 buf[4];
+	struct i2c_msg msg[1] = {
+		{
+			.addr = client->addr,
+			.flags = 0,
+			.len = 2 + len,
+			.buf = buf,
+		}
+	};
+
+	if (2 + len > sizeof(buf))
+		return -EINVAL;
+
+	buf[0] = (reg >> 8) & 0xff;
+	buf[1] = (reg >> 0) & 0xff;
+	memcpy(&buf[2], val, len);
+
+	ret = i2c_transfer(client->adapter, msg, 1);
+	if (ret < 0)
+		return ret;
+	else if (ret != 1)
+		return -EREMOTEIO;
+	else
+		return 0;
+}
+
+
+int mgb4_i2c_init(struct mgb4_i2c_client *client, struct i2c_adapter *adap,
+		  struct i2c_board_info const *info, int addr_size)
+{
+	client->client = i2c_new_client_device(adap, info);
+	if (IS_ERR(client->client))
+		return PTR_ERR(client->client);
+
+	client->addr_size = addr_size;
+
+	return 0;
+}
+
+void mgb4_i2c_free(struct mgb4_i2c_client *client)
+{
+	i2c_unregister_device(client->client);
+}
+
+
+s32 mgb4_i2c_read_byte(struct mgb4_i2c_client *client, u16 reg)
+{
+	int ret;
+	u8 b;
+
+	if (client->addr_size == 8)
+		return i2c_smbus_read_byte_data(client->client, reg);
+
+	ret = read_r16(client->client, reg, &b, 1);
+	if (ret < 0)
+		return ret;
+
+	return (s32)b;
+}
+
+s32 mgb4_i2c_write_byte(struct mgb4_i2c_client *client, u16 reg, u8 val)
+{
+	if (client->addr_size == 8)
+		return i2c_smbus_write_byte_data(client->client, reg, val);
+	else
+		return write_r16(client->client, reg, &val, 1);
+}
+
+s32 mgb4_i2c_mask_byte(struct mgb4_i2c_client *client, u16 reg, u8 mask, u8 val)
+{
+	s32 ret;
+
+	if (mask != 0xFF) {
+		ret = mgb4_i2c_read_byte(client, reg);
+		if (ret < 0)
+			return ret;
+		val |= (u8)ret & ~mask;
+	}
+
+	return mgb4_i2c_write_byte(client, reg, val);
+}
+
+int mgb4_i2c_configure(struct mgb4_i2c_client *client,
+		       const struct mgb4_i2c_kv *values, size_t count)
+{
+	size_t i;
+	s32 res;
+
+	for (i = 0; i < count; i++) {
+		res = mgb4_i2c_mask_byte(client, values[i].reg, values[i].mask,
+		  values[i].val);
+		if (res < 0)
+			return res;
+	}
+
+	return 0;
+}
diff --git a/drivers/media/pci/mgb4/mgb4_i2c.h b/drivers/media/pci/mgb4/mgb4_i2c.h
new file mode 100644
index 000000000000..e5003927509f
--- /dev/null
+++ b/drivers/media/pci/mgb4/mgb4_i2c.h
@@ -0,0 +1,35 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) 2021-2022 Digiteq Automotive
+ *     author: Martin Tuma <martin.tuma@digiteqautomotive.com>
+ */
+
+#ifndef __MGB4_I2C_H__
+#define __MGB4_I2C_H__
+
+#include <linux/i2c.h>
+
+struct mgb4_i2c_client {
+	struct i2c_client *client;
+	int addr_size;
+};
+
+struct mgb4_i2c_kv {
+	u16 reg;
+	u8 mask;
+	u8 val;
+};
+
+extern int mgb4_i2c_init(struct mgb4_i2c_client *client, struct i2c_adapter *adap,
+			 struct i2c_board_info const *info, int addr_size);
+extern void mgb4_i2c_free(struct mgb4_i2c_client *client);
+
+extern s32 mgb4_i2c_read_byte(struct mgb4_i2c_client *client, u16 reg);
+extern s32 mgb4_i2c_write_byte(struct mgb4_i2c_client *client, u16 reg, u8 val);
+extern s32 mgb4_i2c_mask_byte(struct mgb4_i2c_client *client, u16 reg, u8 mask,
+			      u8 val);
+
+extern int mgb4_i2c_configure(struct mgb4_i2c_client *client,
+			      const struct mgb4_i2c_kv *values, size_t count);
+
+#endif
diff --git a/drivers/media/pci/mgb4/mgb4_io.h b/drivers/media/pci/mgb4/mgb4_io.h
new file mode 100644
index 000000000000..dff92065f2c0
--- /dev/null
+++ b/drivers/media/pci/mgb4/mgb4_io.h
@@ -0,0 +1,36 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) 2021-2022 Digiteq Automotive
+ *     author: Martin Tuma <martin.tuma@digiteqautomotive.com>
+ */
+
+#ifndef __MGB4_IO_H__
+#define __MGB4_IO_H__
+
+#include <media/v4l2-dev.h>
+
+#ifndef VFL_TYPE_GRABBER
+#define VFL_TYPE_GRABBER VFL_TYPE_VIDEO
+#endif
+
+#define ERR_NO_REG        0xFFFFFFFE
+#define ERR_QUEUE_TIMEOUT 0xFFFFFFFD
+#define ERR_QUEUE_EMPTY   0xFFFFFFFC
+#define ERR_QUEUE_FULL    0xFFFFFFFB
+
+#define BYTESPERLINE(width, alignment) \
+	(((((width) * 4) - 1) | ((alignment) - 1)) + 1)
+#define PADDING(width, alignment) \
+	((BYTESPERLINE((width), (alignment)) - ((width) * 4)) / 4)
+
+struct frame_buffer {
+	struct vb2_v4l2_buffer vb;
+	struct list_head list;
+};
+
+extern inline struct frame_buffer *to_frame_buffer(struct vb2_v4l2_buffer *vbuf)
+{
+	return container_of(vbuf, struct frame_buffer, vb);
+}
+
+#endif
diff --git a/drivers/media/pci/mgb4/mgb4_regs.c b/drivers/media/pci/mgb4/mgb4_regs.c
new file mode 100644
index 000000000000..53d4e4503a74
--- /dev/null
+++ b/drivers/media/pci/mgb4/mgb4_regs.c
@@ -0,0 +1,30 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2021-2022 Digiteq Automotive
+ *     author: Martin Tuma <martin.tuma@digiteqautomotive.com>
+ */
+
+#include <linux/ioport.h>
+#include "mgb4_regs.h"
+
+int mgb4_regs_map(struct resource *res, struct mgb4_regs *regs)
+{
+	regs->mapbase = res->start;
+	regs->mapsize = res->end - res->start;
+
+	if (!request_mem_region(regs->mapbase, regs->mapsize, res->name))
+		return -EINVAL;
+	regs->membase = ioremap(regs->mapbase, regs->mapsize);
+	if (!regs->membase) {
+		release_mem_region(regs->mapbase, regs->mapsize);
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+void mgb4_regs_free(struct mgb4_regs *regs)
+{
+	iounmap(regs->membase);
+	release_mem_region(regs->mapbase, regs->mapsize);
+}
diff --git a/drivers/media/pci/mgb4/mgb4_regs.h b/drivers/media/pci/mgb4/mgb4_regs.h
new file mode 100644
index 000000000000..1cc16941ea45
--- /dev/null
+++ b/drivers/media/pci/mgb4/mgb4_regs.h
@@ -0,0 +1,35 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) 2021-2022 Digiteq Automotive
+ *     author: Martin Tuma <martin.tuma@digiteqautomotive.com>
+ */
+
+#ifndef __MGB4_REGS_H__
+#define __MGB4_REGS_H__
+
+#include <linux/io.h>
+
+struct mgb4_regs {
+	resource_size_t mapbase;
+	resource_size_t mapsize;
+	void __iomem *membase;
+};
+
+#define mgb4_write_reg(regs, offset, val) \
+	iowrite32(val, (regs)->membase + (offset))
+#define  mgb4_read_reg(regs, offset) \
+	ioread32((regs)->membase + (offset))
+
+static inline void mgb4_mask_reg(struct mgb4_regs *regs, u32 reg, u32 mask,
+				 u32 val)
+{
+	u32 ret = mgb4_read_reg(regs, reg);
+
+	val |= ret & ~mask;
+	mgb4_write_reg(regs, reg, val);
+}
+
+extern int mgb4_regs_map(struct resource *res, struct mgb4_regs *regs);
+extern void mgb4_regs_free(struct mgb4_regs *regs);
+
+#endif
diff --git a/drivers/media/pci/mgb4/mgb4_sysfs.h b/drivers/media/pci/mgb4/mgb4_sysfs.h
new file mode 100644
index 000000000000..2b42a8ba37f7
--- /dev/null
+++ b/drivers/media/pci/mgb4/mgb4_sysfs.h
@@ -0,0 +1,18 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) 2021-2022 Digiteq Automotive
+ *     author: Martin Tuma <martin.tuma@digiteqautomotive.com>
+ */
+
+#ifndef __MGB4_SYSFS_H__
+#define __MGB4_SYSFS_H__
+
+#include <linux/sysfs.h>
+
+extern struct device_attribute *mgb4_pci_attrs[];
+extern struct device_attribute *mgb4_fpdl3_in_attrs[];
+extern struct device_attribute *mgb4_gmsl_in_attrs[];
+extern struct device_attribute *mgb4_fpdl3_out_attrs[];
+extern struct device_attribute *mgb4_gmsl_out_attrs[];
+
+#endif
diff --git a/drivers/media/pci/mgb4/mgb4_sysfs_in.c b/drivers/media/pci/mgb4/mgb4_sysfs_in.c
new file mode 100644
index 000000000000..fc024393c7ba
--- /dev/null
+++ b/drivers/media/pci/mgb4/mgb4_sysfs_in.c
@@ -0,0 +1,750 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2021-2022 Digiteq Automotive
+ *     author: Martin Tuma <martin.tuma@digiteqautomotive.com>
+ */
+
+#include <linux/device.h>
+#include "mgb4_core.h"
+#include "mgb4_i2c.h"
+#include "mgb4_vin.h"
+#include "mgb4_cmt.h"
+#include "mgb4_sysfs.h"
+
+/* Common for both FPDL3 and GMSL */
+
+static ssize_t read_input_id(struct device *dev,
+			     struct device_attribute *attr, char *buf)
+{
+	struct video_device *vdev = to_video_device(dev);
+	struct mgb4_vin_dev *vindev = video_get_drvdata(vdev);
+
+	return sprintf(buf, "%d\n", vindev->config->id);
+}
+
+static ssize_t read_oldi_lane_width(struct device *dev,
+				    struct device_attribute *attr, char *buf)
+{
+	struct video_device *vdev = to_video_device(dev);
+	struct mgb4_vin_dev *vindev = video_get_drvdata(vdev);
+	u16 i2c_reg;
+	u8 i2c_mask, i2c_single_val, i2c_dual_val;
+	u32 config;
+	int ret;
+
+	i2c_reg = MGB4_IS_GMSL(vindev->mgbdev) ? 0x1CE : 0x49;
+	i2c_mask = MGB4_IS_GMSL(vindev->mgbdev) ? 0x0E : 0x03;
+	i2c_single_val = MGB4_IS_GMSL(vindev->mgbdev) ? 0x00 : 0x02;
+	i2c_dual_val = MGB4_IS_GMSL(vindev->mgbdev) ? 0x0E : 0x00;
+
+	mutex_lock(&vindev->mgbdev->i2c_lock);
+	ret = mgb4_i2c_read_byte(&vindev->deser, i2c_reg);
+	mutex_unlock(&vindev->mgbdev->i2c_lock);
+	if (ret < 0)
+		return -EIO;
+
+	config = mgb4_read_reg(&vindev->mgbdev->video, vindev->config->regs.config);
+
+	if (((config & (1U<<9)) && ((ret & i2c_mask) != i2c_dual_val))
+	  || (!(config & (1U<<9)) && ((ret & i2c_mask) != i2c_single_val))) {
+		dev_err(dev, "I2C/FPGA register value mismatch\n");
+		return -EINVAL;
+	}
+
+	return sprintf(buf, "%s\n", config & (1U<<9) ? "1" : "0");
+}
+
+static ssize_t write_oldi_lane_width(struct device *dev,
+				     struct device_attribute *attr,
+				     const char *buf, size_t count)
+{
+	struct video_device *vdev = to_video_device(dev);
+	struct mgb4_vin_dev *vindev = video_get_drvdata(vdev);
+	u32 fpga_data;
+	u16 i2c_reg;
+	u8 i2c_mask, i2c_data;
+	unsigned long val;
+	int ret;
+
+	ret = kstrtoul(buf, 10, &val);
+	if (ret)
+		return ret;
+
+	switch (val) {
+	case 0: /* single */
+		fpga_data = 0;
+		i2c_data = MGB4_IS_GMSL(vindev->mgbdev) ? 0x00 : 0x02;
+		break;
+	case 1: /* dual */
+		fpga_data = 1U<<9;
+		i2c_data = MGB4_IS_GMSL(vindev->mgbdev) ? 0x0E : 0x00;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	i2c_reg = MGB4_IS_GMSL(vindev->mgbdev) ? 0x1CE : 0x49;
+	i2c_mask = MGB4_IS_GMSL(vindev->mgbdev) ? 0x0E : 0x03;
+
+	mutex_lock(&vindev->mgbdev->i2c_lock);
+	ret = mgb4_i2c_mask_byte(&vindev->deser, i2c_reg, i2c_mask, i2c_data);
+	mutex_unlock(&vindev->mgbdev->i2c_lock);
+	if (ret < 0)
+		return -EIO;
+	mgb4_mask_reg(&vindev->mgbdev->video, vindev->config->regs.config, 1U<<9,
+	  fpga_data);
+	if (MGB4_IS_GMSL(vindev->mgbdev)) {
+		/* reset input link */
+		mutex_lock(&vindev->mgbdev->i2c_lock);
+		ret = mgb4_i2c_mask_byte(&vindev->deser, 0x10, 1U<<5, 1U<<5);
+		mutex_unlock(&vindev->mgbdev->i2c_lock);
+		if (ret < 0)
+			return -EIO;
+	}
+
+	return count;
+}
+
+static ssize_t read_color_mapping(struct device *dev,
+				  struct device_attribute *attr, char *buf)
+{
+	struct video_device *vdev = to_video_device(dev);
+	struct mgb4_vin_dev *vindev = video_get_drvdata(vdev);
+	u32 config = mgb4_read_reg(&vindev->mgbdev->video,
+	  vindev->config->regs.config);
+
+	return sprintf(buf, "%s\n", config & (1U<<8) ? "0" : "1");
+}
+
+static ssize_t write_color_mapping(struct device *dev,
+				   struct device_attribute *attr,
+				   const char *buf, size_t count)
+{
+	struct video_device *vdev = to_video_device(dev);
+	struct mgb4_vin_dev *vindev = video_get_drvdata(vdev);
+	u32 fpga_data;
+	unsigned long val;
+	int ret;
+
+	ret = kstrtoul(buf, 10, &val);
+	if (ret)
+		return ret;
+
+	switch (val) {
+	case 0: /* OLDI/JEIDA */
+		fpga_data = (1U<<8);
+		break;
+	case 1: /* SPWG/VESA */
+		fpga_data = 0;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	mgb4_mask_reg(&vindev->mgbdev->video, vindev->config->regs.config, 1U<<8,
+	  fpga_data);
+
+	return count;
+}
+
+static ssize_t read_link_status(struct device *dev, struct device_attribute *attr,
+				char *buf)
+{
+	struct video_device *vdev = to_video_device(dev);
+	struct mgb4_vin_dev *vindev = video_get_drvdata(vdev);
+	u32 status = mgb4_read_reg(&vindev->mgbdev->video, vindev->config->regs.status);
+
+	return sprintf(buf, "%s\n", status & (1U<<2) ? "1" : "0");
+}
+
+static ssize_t read_stream_status(struct device *dev, struct device_attribute *attr,
+				  char *buf)
+{
+	struct video_device *vdev = to_video_device(dev);
+	struct mgb4_vin_dev *vindev = video_get_drvdata(vdev);
+	u32 status = mgb4_read_reg(&vindev->mgbdev->video, vindev->config->regs.status);
+
+	return sprintf(buf, "%s\n", ((status & (1<<14)) && (status & (1<<2))
+	  && (status & (3<<9))) ? "1" : "0");
+}
+
+static ssize_t read_hsync_status(struct device *dev, struct device_attribute *attr,
+				 char *buf)
+{
+	struct video_device *vdev = to_video_device(dev);
+	struct mgb4_vin_dev *vindev = video_get_drvdata(vdev);
+	u32 status = mgb4_read_reg(&vindev->mgbdev->video, vindev->config->regs.status);
+	u32 res;
+
+	if (!(status & (1U<<11)))
+		res = 0x02; // not available
+	else if (status & (1U<<12))
+		res = 0x01; // active high
+	else
+		res = 0x00; // active low
+
+	return sprintf(buf, "%u\n", res);
+}
+
+static ssize_t read_vsync_status(struct device *dev, struct device_attribute *attr,
+				 char *buf)
+{
+	struct video_device *vdev = to_video_device(dev);
+	struct mgb4_vin_dev *vindev = video_get_drvdata(vdev);
+	u32 status = mgb4_read_reg(&vindev->mgbdev->video, vindev->config->regs.status);
+	u32 res;
+
+	if (!(status & (1U<<11)))
+		res = 0x02; // not available
+	else if (status & (1U<<13))
+		res = 0x01; // active high
+	else
+		res = 0x00; // active low
+
+	return sprintf(buf, "%u\n", res);
+}
+
+static ssize_t read_hsync_gap(struct device *dev, struct device_attribute *attr,
+			      char *buf)
+{
+	struct video_device *vdev = to_video_device(dev);
+	struct mgb4_vin_dev *vindev = video_get_drvdata(vdev);
+	u32 sync = mgb4_read_reg(&vindev->mgbdev->video, vindev->config->regs.sync);
+
+	return sprintf(buf, "%u\n", sync >> 16);
+}
+
+static ssize_t write_hsync_gap(struct device *dev,
+			       struct device_attribute *attr, const char *buf,
+			       size_t count)
+{
+	struct video_device *vdev = to_video_device(dev);
+	struct mgb4_vin_dev *vindev = video_get_drvdata(vdev);
+	unsigned long val;
+	int ret;
+
+	ret = kstrtoul(buf, 10, &val);
+	if (ret)
+		return ret;
+	if (val > 0xFFFF)
+		return -EINVAL;
+
+	mgb4_mask_reg(&vindev->mgbdev->video, vindev->config->regs.sync, 0xFFFF0000,
+	  val << 16);
+
+	return count;
+}
+
+static ssize_t read_vsync_gap(struct device *dev, struct device_attribute *attr,
+			      char *buf)
+{
+	struct video_device *vdev = to_video_device(dev);
+	struct mgb4_vin_dev *vindev = video_get_drvdata(vdev);
+	u32 sync = mgb4_read_reg(&vindev->mgbdev->video, vindev->config->regs.sync);
+
+	return sprintf(buf, "%u\n", sync & 0xFFFF);
+}
+
+static ssize_t write_vsync_gap(struct device *dev,
+			       struct device_attribute *attr, const char *buf,
+			       size_t count)
+{
+	struct video_device *vdev = to_video_device(dev);
+	struct mgb4_vin_dev *vindev = video_get_drvdata(vdev);
+	unsigned long val;
+	int ret;
+
+	ret = kstrtoul(buf, 10, &val);
+	if (ret)
+		return ret;
+	if (val > 0xFFFF)
+		return -EINVAL;
+
+	mgb4_mask_reg(&vindev->mgbdev->video, vindev->config->regs.sync, 0xFFFF, val);
+
+	return count;
+}
+
+static ssize_t read_pclk_frequency(struct device *dev,
+				   struct device_attribute *attr, char *buf)
+{
+	struct video_device *vdev = to_video_device(dev);
+	struct mgb4_vin_dev *vindev = video_get_drvdata(vdev);
+	u32 freq = mgb4_read_reg(&vindev->mgbdev->video, vindev->config->regs.pclk);
+
+	return sprintf(buf, "%u\n", freq);
+}
+
+static ssize_t read_hsync_width(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	struct video_device *vdev = to_video_device(dev);
+	struct mgb4_vin_dev *vindev = video_get_drvdata(vdev);
+	u32 sig = mgb4_read_reg(&vindev->mgbdev->video, vindev->config->regs.signal);
+
+	return sprintf(buf, "%u\n", (sig & 0x00FF0000) >> 16);
+}
+
+static ssize_t read_vsync_width(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	struct video_device *vdev = to_video_device(dev);
+	struct mgb4_vin_dev *vindev = video_get_drvdata(vdev);
+	u32 sig = mgb4_read_reg(&vindev->mgbdev->video, vindev->config->regs.signal2);
+
+	return sprintf(buf, "%u\n", (sig & 0x00FF0000) >> 16);
+}
+
+static ssize_t read_hback_porch(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	struct video_device *vdev = to_video_device(dev);
+	struct mgb4_vin_dev *vindev = video_get_drvdata(vdev);
+	u32 sig = mgb4_read_reg(&vindev->mgbdev->video, vindev->config->regs.signal);
+
+	return sprintf(buf, "%u\n", (sig & 0x0000FF00) >> 8);
+}
+
+static ssize_t read_hfront_porch(struct device *dev,
+				 struct device_attribute *attr, char *buf)
+{
+	struct video_device *vdev = to_video_device(dev);
+	struct mgb4_vin_dev *vindev = video_get_drvdata(vdev);
+	u32 sig = mgb4_read_reg(&vindev->mgbdev->video, vindev->config->regs.signal);
+
+	return sprintf(buf, "%u\n", (sig & 0x000000FF));
+}
+
+static ssize_t read_vback_porch(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	struct video_device *vdev = to_video_device(dev);
+	struct mgb4_vin_dev *vindev = video_get_drvdata(vdev);
+	u32 sig = mgb4_read_reg(&vindev->mgbdev->video, vindev->config->regs.signal2);
+
+	return sprintf(buf, "%u\n", (sig & 0x0000FF00) >> 8);
+}
+
+static ssize_t read_vfront_porch(struct device *dev,
+				 struct device_attribute *attr, char *buf)
+{
+	struct video_device *vdev = to_video_device(dev);
+	struct mgb4_vin_dev *vindev = video_get_drvdata(vdev);
+	u32 sig = mgb4_read_reg(&vindev->mgbdev->video, vindev->config->regs.signal2);
+
+	return sprintf(buf, "%u\n", (sig & 0x000000FF));
+}
+
+static ssize_t read_frequency_range(struct device *dev,
+				    struct device_attribute *attr, char *buf)
+{
+	struct video_device *vdev = to_video_device(dev);
+	struct mgb4_vin_dev *vindev = video_get_drvdata(vdev);
+
+	return sprintf(buf, "%d\n", vindev->freq_range);
+}
+
+static ssize_t write_frequency_range(struct device *dev,
+				     struct device_attribute *attr,
+				     const char *buf, size_t count)
+{
+	struct video_device *vdev = to_video_device(dev);
+	struct mgb4_vin_dev *vindev = video_get_drvdata(vdev);
+	unsigned long val, flags;
+	int ret;
+
+	ret = kstrtoul(buf, 10, &val);
+	if (ret)
+		return ret;
+	if (val > 1)
+		return -EINVAL;
+
+	spin_lock_irqsave(&(vindev->vdev.fh_lock), flags);
+	if (!list_empty(&(vindev->vdev.fh_list))) {
+		spin_unlock_irqrestore(&(vindev->vdev.fh_lock), flags);
+		return -EBUSY;
+	}
+
+	mgb4_cmt_set_vin(vindev, val);
+	vindev->freq_range = val;
+
+	spin_unlock_irqrestore(&(vindev->vdev.fh_lock), flags);
+
+	return count;
+}
+
+static ssize_t read_alignment(struct device *dev,
+			      struct device_attribute *attr, char *buf)
+{
+	struct video_device *vdev = to_video_device(dev);
+	struct mgb4_vin_dev *vindev = video_get_drvdata(vdev);
+
+	return sprintf(buf, "%d\n", vindev->alignment);
+}
+
+static ssize_t write_alignment(struct device *dev,
+			       struct device_attribute *attr, const char *buf,
+			       size_t count)
+{
+	struct video_device *vdev = to_video_device(dev);
+	struct mgb4_vin_dev *vindev = video_get_drvdata(vdev);
+	unsigned long val, flags;
+	int ret;
+
+	ret = kstrtoul(buf, 10, &val);
+	if (ret)
+		return ret;
+	if (!val || (val & (val - 1)))
+		return -EINVAL;
+
+	spin_lock_irqsave(&(vindev->vdev.fh_lock), flags);
+	if (!list_empty(&(vindev->vdev.fh_list))) {
+		spin_unlock_irqrestore(&(vindev->vdev.fh_lock), flags);
+		return -EBUSY;
+	}
+
+	vindev->alignment = val;
+
+	spin_unlock_irqrestore(&(vindev->vdev.fh_lock), flags);
+
+	return count;
+}
+
+/* FPDL3 only */
+
+static ssize_t read_fpdl3_input_width(struct device *dev,
+				      struct device_attribute *attr, char *buf)
+{
+	struct video_device *vdev = to_video_device(dev);
+	struct mgb4_vin_dev *vindev = video_get_drvdata(vdev);
+	s32 ret;
+
+	mutex_lock(&vindev->mgbdev->i2c_lock);
+	ret = mgb4_i2c_read_byte(&vindev->deser, 0x34);
+	mutex_unlock(&vindev->mgbdev->i2c_lock);
+	if (ret < 0)
+		return -EIO;
+
+	switch ((u8)ret & 0x18) {
+	case 0:
+		return sprintf(buf, "0\n");
+	case 0x10:
+		return sprintf(buf, "1\n");
+	case 0x08:
+		return sprintf(buf, "2\n");
+	default:
+		return -EINVAL;
+	}
+}
+
+static ssize_t write_fpdl3_input_width(struct device *dev,
+				       struct device_attribute *attr,
+				       const char *buf, size_t count)
+{
+	struct video_device *vdev = to_video_device(dev);
+	struct mgb4_vin_dev *vindev = video_get_drvdata(vdev);
+	u8 i2c_data;
+	unsigned long val;
+	int ret;
+
+	ret = kstrtoul(buf, 10, &val);
+	if (ret)
+		return ret;
+
+	switch (val) {
+	case 0: /* auto */
+		i2c_data = 0x00;
+		break;
+	case 1: /* single */
+		i2c_data = 0x10;
+		break;
+	case 2: /* dual */
+		i2c_data = 0x08;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	mutex_lock(&vindev->mgbdev->i2c_lock);
+	ret = mgb4_i2c_mask_byte(&vindev->deser, 0x34, 0x18, i2c_data);
+	mutex_unlock(&vindev->mgbdev->i2c_lock);
+	if (ret < 0)
+		return -EIO;
+
+	return count;
+}
+
+
+/* GMSL only */
+
+static ssize_t read_gmsl_mode(struct device *dev,
+			      struct device_attribute *attr, char *buf)
+{
+	struct video_device *vdev = to_video_device(dev);
+	struct mgb4_vin_dev *vindev = video_get_drvdata(vdev);
+	s32 r1, r300, r3;
+
+	mutex_lock(&vindev->mgbdev->i2c_lock);
+	r1 = mgb4_i2c_read_byte(&vindev->deser, 0x01);
+	r300 = mgb4_i2c_read_byte(&vindev->deser, 0x300);
+	r3 = mgb4_i2c_read_byte(&vindev->deser, 0x03);
+	mutex_unlock(&vindev->mgbdev->i2c_lock);
+	if (r1 < 0 || r300 < 0 || r3 < 0)
+		return -EIO;
+
+	if ((r1 & 0x03) == 0x03 && (r300 & 0x0C) == 0x0C && (r3 & 0xC0) == 0xC0)
+		return sprintf(buf, "0\n");
+	else if ((r1 & 0x03) == 0x02 && (r300 & 0x0C) == 0x08 && (r3 & 0xC0) == 0x00)
+		return sprintf(buf, "1\n");
+	else if ((r1 & 0x03) == 0x01 && (r300 & 0x0C) == 0x04 && (r3 & 0xC0) == 0x00)
+		return sprintf(buf, "2\n");
+	else if ((r1 & 0x03) == 0x00 && (r300 & 0x0C) == 0x00 && (r3 & 0xC0) == 0x00)
+		return sprintf(buf, "3\n");
+	else
+		return -EINVAL;
+}
+
+static ssize_t write_gmsl_mode(struct device *dev,
+			       struct device_attribute *attr, const char *buf,
+			       size_t count)
+{
+	static const struct mgb4_i2c_kv G12[] = {
+		{0x01, 0x03, 0x03}, {0x300, 0x0C, 0x0C}, {0x03, 0xC0, 0xC0}};
+	static const struct mgb4_i2c_kv G6[] = {
+		{0x01, 0x03, 0x02}, {0x300, 0x0C, 0x08}, {0x03, 0xC0, 0x00}};
+	static const struct mgb4_i2c_kv G3[] = {
+		{0x01, 0x03, 0x01}, {0x300, 0x0C, 0x04}, {0x03, 0xC0, 0x00}};
+	static const struct mgb4_i2c_kv G1[] = {
+		{0x01, 0x03, 0x00}, {0x300, 0x0C, 0x00}, {0x03, 0xC0, 0x00}};
+	static const struct mgb4_i2c_kv reset[] = {
+		{0x10, 1U<<5, 1U<<5}, {0x300, 1U<<6, 1U<<6}};
+	struct video_device *vdev = to_video_device(dev);
+	struct mgb4_vin_dev *vindev = video_get_drvdata(vdev);
+	const struct mgb4_i2c_kv *values;
+	unsigned long val;
+	int ret;
+
+	ret = kstrtoul(buf, 10, &val);
+	if (ret)
+		return ret;
+
+	switch (val) {
+	case 0: /* 12Gb/s */
+		values = G12;
+		break;
+	case 1: /* 6Gb/s */
+		values = G6;
+		break;
+	case 2: /* 3Gb/s */
+		values = G3;
+		break;
+	case 3: /* 1.5Gb/s */
+		values = G1;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	mutex_lock(&vindev->mgbdev->i2c_lock);
+	ret = mgb4_i2c_configure(&vindev->deser, values, 3);
+	ret |= mgb4_i2c_configure(&vindev->deser, reset, 2);
+	mutex_unlock(&vindev->mgbdev->i2c_lock);
+	if (ret < 0)
+		return -EIO;
+
+	return count;
+}
+
+static ssize_t read_gmsl_stream_id(struct device *dev,
+				   struct device_attribute *attr, char *buf)
+{
+	struct video_device *vdev = to_video_device(dev);
+	struct mgb4_vin_dev *vindev = video_get_drvdata(vdev);
+	s32 ret;
+
+	mutex_lock(&vindev->mgbdev->i2c_lock);
+	ret = mgb4_i2c_read_byte(&vindev->deser, 0xA0);
+	mutex_unlock(&vindev->mgbdev->i2c_lock);
+	if (ret < 0)
+		return -EIO;
+
+	return sprintf(buf, "%d\n", ret & 0x03);
+}
+
+static ssize_t write_gmsl_stream_id(struct device *dev,
+				    struct device_attribute *attr,
+				    const char *buf, size_t count)
+{
+	struct video_device *vdev = to_video_device(dev);
+	struct mgb4_vin_dev *vindev = video_get_drvdata(vdev);
+	unsigned long val, flags;
+	int ret;
+
+	ret = kstrtoul(buf, 10, &val);
+	if (ret)
+		return ret;
+	if (val > 3)
+		return -EINVAL;
+
+	spin_lock_irqsave(&(vindev->vdev.fh_lock), flags);
+	ret = list_empty(&(vindev->vdev.fh_list));
+	spin_unlock_irqrestore(&(vindev->vdev.fh_lock), flags);
+	if (!ret)
+		return -EBUSY;
+
+	/* Formaly, there is a race condition here as the change should be formaly
+	 * done under the spinlock, but we only want to prevent a resolution change
+	 * where possible. However, resolution changes can happen anyway and the
+	 * driver can handle them (they only break the image, not the system).
+	 *
+	 * So instead of trying to workaround the spinlock - mgb4_i2c_mask_byte()
+	 * does sleep - we simply let the rare race condition happen...
+	 */
+	mutex_lock(&vindev->mgbdev->i2c_lock);
+	ret = mgb4_i2c_mask_byte(&vindev->deser, 0xA0, 0x03, (u8)val);
+	mutex_unlock(&vindev->mgbdev->i2c_lock);
+	if (ret < 0)
+		return -EIO;
+
+	return count;
+}
+
+static ssize_t read_gmsl_fec(struct device *dev, struct device_attribute *attr,
+			     char *buf)
+{
+	struct video_device *vdev = to_video_device(dev);
+	struct mgb4_vin_dev *vindev = video_get_drvdata(vdev);
+	s32 r3e0, r308;
+
+	mutex_lock(&vindev->mgbdev->i2c_lock);
+	r3e0 = mgb4_i2c_read_byte(&vindev->deser, 0x3E0);
+	r308 = mgb4_i2c_read_byte(&vindev->deser, 0x308);
+	mutex_unlock(&vindev->mgbdev->i2c_lock);
+	if (r3e0 < 0 || r308 < 0)
+		return -EIO;
+
+	if ((r3e0 & 0x07) == 0x00 && (r308 & 0x01) == 0x00)
+		return sprintf(buf, "0\n");
+	else if ((r3e0 & 0x07) == 0x07 && (r308 & 0x01) == 0x01)
+		return sprintf(buf, "1\n");
+	else
+		return -EINVAL;
+}
+
+static ssize_t write_gmsl_fec(struct device *dev, struct device_attribute *attr,
+			      const char *buf, size_t count)
+{
+	struct video_device *vdev = to_video_device(dev);
+	struct mgb4_vin_dev *vindev = video_get_drvdata(vdev);
+	static const struct mgb4_i2c_kv enable[] = {
+		{0x3E0, 0x07, 0x07}, {0x308, 0x01, 0x01}};
+	static const struct mgb4_i2c_kv disable[] = {
+		{0x3E0, 0x07, 0x00}, {0x308, 0x01, 0x00}};
+	static const struct mgb4_i2c_kv reset[] = {
+		{0x10, 1U<<5, 1U<<5}, {0x300, 1U<<6, 1U<<6}};
+	const struct mgb4_i2c_kv *values;
+	unsigned long val;
+	int ret;
+
+	ret = kstrtoul(buf, 10, &val);
+	if (ret)
+		return ret;
+
+	switch (val) {
+	case 0: /* disabled */
+		values = disable;
+		break;
+	case 1: /* enabled */
+		values = enable;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	mutex_lock(&vindev->mgbdev->i2c_lock);
+	ret = mgb4_i2c_configure(&vindev->deser, values, 2);
+	ret |= mgb4_i2c_configure(&vindev->deser, reset, 2);
+	mutex_unlock(&vindev->mgbdev->i2c_lock);
+	if (ret < 0)
+		return -EIO;
+
+	return count;
+}
+
+static DEVICE_ATTR(input_id, 0444, read_input_id, NULL);
+static DEVICE_ATTR(oldi_lane_width, 0644, read_oldi_lane_width,
+		   write_oldi_lane_width);
+static DEVICE_ATTR(color_mapping, 0644, read_color_mapping,
+		   write_color_mapping);
+static DEVICE_ATTR(link_status, 0444, read_link_status, NULL);
+static DEVICE_ATTR(stream_status, 0444, read_stream_status, NULL);
+static DEVICE_ATTR(hsync_status, 0444, read_hsync_status, NULL);
+static DEVICE_ATTR(vsync_status, 0444, read_vsync_status, NULL);
+static DEVICE_ATTR(hsync_gap_length, 0644, read_hsync_gap, write_hsync_gap);
+static DEVICE_ATTR(vsync_gap_length, 0644, read_vsync_gap, write_vsync_gap);
+static DEVICE_ATTR(pclk_frequency, 0444, read_pclk_frequency, NULL);
+static DEVICE_ATTR(hsync_width, 0444, read_hsync_width, NULL);
+static DEVICE_ATTR(vsync_width, 0444, read_vsync_width, NULL);
+static DEVICE_ATTR(hback_porch, 0444, read_hback_porch, NULL);
+static DEVICE_ATTR(hfront_porch, 0444, read_hfront_porch, NULL);
+static DEVICE_ATTR(vback_porch, 0444, read_vback_porch, NULL);
+static DEVICE_ATTR(vfront_porch, 0444, read_vfront_porch, NULL);
+static DEVICE_ATTR(frequency_range, 0644, read_frequency_range,
+		   write_frequency_range);
+static DEVICE_ATTR(alignment, 0644, read_alignment, write_alignment);
+
+static DEVICE_ATTR(fpdl3_input_width, 0644, read_fpdl3_input_width,
+		   write_fpdl3_input_width);
+
+static DEVICE_ATTR(gmsl_mode, 0644, read_gmsl_mode, write_gmsl_mode);
+static DEVICE_ATTR(gmsl_stream_id, 0644, read_gmsl_stream_id,
+		   write_gmsl_stream_id);
+static DEVICE_ATTR(gmsl_fec, 0644, read_gmsl_fec, write_gmsl_fec);
+
+struct device_attribute *mgb4_fpdl3_in_attrs[] = {
+	&dev_attr_input_id,
+	&dev_attr_link_status,
+	&dev_attr_stream_status,
+	&dev_attr_hsync_status,
+	&dev_attr_vsync_status,
+	&dev_attr_oldi_lane_width,
+	&dev_attr_color_mapping,
+	&dev_attr_hsync_gap_length,
+	&dev_attr_vsync_gap_length,
+	&dev_attr_pclk_frequency,
+	&dev_attr_hsync_width,
+	&dev_attr_vsync_width,
+	&dev_attr_hback_porch,
+	&dev_attr_hfront_porch,
+	&dev_attr_vback_porch,
+	&dev_attr_vfront_porch,
+	&dev_attr_frequency_range,
+	&dev_attr_alignment,
+	&dev_attr_fpdl3_input_width,
+	NULL
+};
+
+struct device_attribute *mgb4_gmsl_in_attrs[] = {
+	&dev_attr_input_id,
+	&dev_attr_link_status,
+	&dev_attr_stream_status,
+	&dev_attr_hsync_status,
+	&dev_attr_vsync_status,
+	&dev_attr_oldi_lane_width,
+	&dev_attr_color_mapping,
+	&dev_attr_hsync_gap_length,
+	&dev_attr_vsync_gap_length,
+	&dev_attr_pclk_frequency,
+	&dev_attr_hsync_width,
+	&dev_attr_vsync_width,
+	&dev_attr_hback_porch,
+	&dev_attr_hfront_porch,
+	&dev_attr_vback_porch,
+	&dev_attr_vfront_porch,
+	&dev_attr_frequency_range,
+	&dev_attr_alignment,
+	&dev_attr_gmsl_mode,
+	&dev_attr_gmsl_stream_id,
+	&dev_attr_gmsl_fec,
+	NULL
+};
diff --git a/drivers/media/pci/mgb4/mgb4_sysfs_out.c b/drivers/media/pci/mgb4/mgb4_sysfs_out.c
new file mode 100644
index 000000000000..be80b635d1d7
--- /dev/null
+++ b/drivers/media/pci/mgb4/mgb4_sysfs_out.c
@@ -0,0 +1,734 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2021-2022 Digiteq Automotive
+ *     author: Martin Tuma <martin.tuma@digiteqautomotive.com>
+ */
+
+#include <linux/device.h>
+#include "mgb4_core.h"
+#include "mgb4_i2c.h"
+#include "mgb4_vout.h"
+#include "mgb4_vin.h"
+#include "mgb4_cmt.h"
+#include "mgb4_sysfs.h"
+
+static int loopin_cnt(struct mgb4_vin_dev *vindev)
+{
+	struct mgb4_vout_dev *voutdev;
+	u32 config;
+	int i, cnt = 0;
+
+	for (i = 0; i < MGB4_VOUT_DEVICES; i++) {
+		voutdev = vindev->mgbdev->vout[i];
+		if (!voutdev)
+			continue;
+
+		config = mgb4_read_reg(&voutdev->mgbdev->video,
+		  voutdev->config->regs.config);
+		if ((config & 0xc) >> 2 == vindev->config->id)
+			cnt++;
+	}
+
+	return cnt;
+}
+
+
+/* Common for both FPDL3 and GMSL */
+
+static ssize_t read_output_id(struct device *dev,
+			      struct device_attribute *attr, char *buf)
+{
+	struct video_device *vdev = to_video_device(dev);
+	struct mgb4_vout_dev *voutdev = video_get_drvdata(vdev);
+
+	return sprintf(buf, "%d\n", voutdev->config->id);
+}
+
+static ssize_t read_video_source(struct device *dev,
+				 struct device_attribute *attr, char *buf)
+{
+	struct video_device *vdev = to_video_device(dev);
+	struct mgb4_vout_dev *voutdev = video_get_drvdata(vdev);
+	u32 config = mgb4_read_reg(&voutdev->mgbdev->video,
+	  voutdev->config->regs.config);
+
+	return sprintf(buf, "%u\n", (config & 0xc) >> 2);
+}
+
+static ssize_t write_video_source(struct device *dev,
+				  struct device_attribute *attr,
+				  const char *buf, size_t count)
+{
+	struct video_device *vdev = to_video_device(dev);
+	struct mgb4_vout_dev *voutdev = video_get_drvdata(vdev);
+	struct mgb4_dev *mgbdev = voutdev->mgbdev;
+	struct mgb4_vin_dev *loopin_new = 0, *loopin_old = 0;
+	unsigned long val;
+	unsigned long flags_in[MGB4_VIN_DEVICES], flags_out[MGB4_VOUT_DEVICES];
+	ssize_t ret;
+	u32 config;
+	int i;
+
+
+	memset(flags_in, 0, sizeof(flags_in));
+	memset(flags_out, 0, sizeof(flags_out));
+
+	ret = kstrtoul(buf, 10, &val);
+	if (ret)
+		return ret;
+	if (val > 3)
+		return -EINVAL;
+
+	for (i = 0; i < MGB4_VIN_DEVICES; i++)
+		if (mgbdev->vin[i])
+			spin_lock_irqsave(&(mgbdev->vin[i]->vdev.fh_lock), flags_in[i]);
+	for (i = 0; i < MGB4_VOUT_DEVICES; i++)
+		if (mgbdev->vout[i])
+			spin_lock_irqsave(&(mgbdev->vout[i]->vdev.fh_lock), flags_out[i]);
+
+	ret = -EBUSY;
+	for (i = 0; i < MGB4_VIN_DEVICES; i++)
+		if (mgbdev->vin[i] && !list_empty(&(mgbdev->vin[i]->vdev.fh_list)))
+			goto error;
+	for (i = 0; i < MGB4_VOUT_DEVICES; i++)
+		if (mgbdev->vout[i] && !list_empty(&(mgbdev->vout[i]->vdev.fh_list)))
+			goto error;
+
+	config = mgb4_read_reg(&mgbdev->video, voutdev->config->regs.config);
+
+	if (((config & 0xc) >> 2) < MGB4_VIN_DEVICES)
+		loopin_old = mgbdev->vin[(config & 0xc) >> 2];
+	if (val < MGB4_VIN_DEVICES)
+		loopin_new = mgbdev->vin[val];
+	if (loopin_old && loopin_cnt(loopin_old) == 1)
+		mgb4_mask_reg(&mgbdev->video, loopin_old->config->regs.config, 0x2, 0x0);
+	if (loopin_new)
+		mgb4_mask_reg(&mgbdev->video, loopin_new->config->regs.config, 0x2, 0x2);
+
+	if (val == voutdev->config->id + MGB4_VIN_DEVICES)
+		mgb4_write_reg(&mgbdev->video, voutdev->config->regs.config,
+		  config & ~(1<<1));
+	else
+		mgb4_write_reg(&mgbdev->video, voutdev->config->regs.config,
+		  config | (1U<<1));
+
+	mgb4_mask_reg(&mgbdev->video, voutdev->config->regs.config, 0xc, val << 2);
+
+	ret = count;
+
+error:
+	for (i = MGB4_VOUT_DEVICES - 1; i >= 0; i--)
+		if (mgbdev->vout[i])
+			spin_unlock_irqrestore(&(mgbdev->vout[i]->vdev.fh_lock), flags_out[i]);
+	for (i = MGB4_VIN_DEVICES - 1; i >= 0; i--)
+		if (mgbdev->vin[i])
+			spin_unlock_irqrestore(&(mgbdev->vin[i]->vdev.fh_lock), flags_in[i]);
+
+	return ret;
+}
+
+static ssize_t read_display_width(struct device *dev,
+				  struct device_attribute *attr, char *buf)
+{
+	struct video_device *vdev = to_video_device(dev);
+	struct mgb4_vout_dev *voutdev = video_get_drvdata(vdev);
+	u32 config = mgb4_read_reg(&voutdev->mgbdev->video,
+	  voutdev->config->regs.resolution);
+
+	return sprintf(buf, "%u\n", config >> 16);
+}
+
+static ssize_t write_display_width(struct device *dev,
+				   struct device_attribute *attr,
+				   const char *buf, size_t count)
+{
+	struct video_device *vdev = to_video_device(dev);
+	struct mgb4_vout_dev *voutdev = video_get_drvdata(vdev);
+	unsigned long val, flags;
+	int ret, busy;
+
+	ret = kstrtoul(buf, 10, &val);
+	if (ret)
+		return ret;
+	if (val > 0xFFFF)
+		return -EINVAL;
+
+	spin_lock_irqsave(&(voutdev->vdev.fh_lock), flags);
+	busy = !list_empty(&(voutdev->vdev.fh_list));
+	if (busy) {
+		spin_unlock_irqrestore(&(voutdev->vdev.fh_lock), flags);
+		return -EBUSY;
+	}
+
+	mgb4_mask_reg(&voutdev->mgbdev->video, voutdev->config->regs.resolution,
+	  0xFFFF0000, val << 16);
+
+	spin_unlock_irqrestore(&(voutdev->vdev.fh_lock), flags);
+
+	return count;
+}
+
+static ssize_t read_display_height(struct device *dev,
+				   struct device_attribute *attr, char *buf)
+{
+	struct video_device *vdev = to_video_device(dev);
+	struct mgb4_vout_dev *voutdev = video_get_drvdata(vdev);
+	u32 config = mgb4_read_reg(&voutdev->mgbdev->video,
+	  voutdev->config->regs.resolution);
+
+	return sprintf(buf, "%u\n", config & 0xFFFF);
+}
+
+static ssize_t write_display_height(struct device *dev,
+				    struct device_attribute *attr,
+				    const char *buf, size_t count)
+{
+	struct video_device *vdev = to_video_device(dev);
+	struct mgb4_vout_dev *voutdev = video_get_drvdata(vdev);
+	unsigned long val, flags;
+	int ret, busy;
+
+	ret = kstrtoul(buf, 10, &val);
+	if (ret)
+		return ret;
+	if (val > 0xFFFF)
+		return -EINVAL;
+
+	spin_lock_irqsave(&(voutdev->vdev.fh_lock), flags);
+	busy = !list_empty(&(voutdev->vdev.fh_list));
+	if (busy) {
+		spin_unlock_irqrestore(&(voutdev->vdev.fh_lock), flags);
+		return -EBUSY;
+	}
+
+	mgb4_mask_reg(&voutdev->mgbdev->video, voutdev->config->regs.resolution,
+	  0xFFFF, val);
+
+	spin_unlock_irqrestore(&(voutdev->vdev.fh_lock), flags);
+
+	return count;
+}
+
+static ssize_t read_frame_rate(struct device *dev,
+			       struct device_attribute *attr, char *buf)
+{
+	struct video_device *vdev = to_video_device(dev);
+	struct mgb4_vout_dev *voutdev = video_get_drvdata(vdev);
+	u32 period = mgb4_read_reg(&voutdev->mgbdev->video,
+	  voutdev->config->regs.frame_period);
+
+	return sprintf(buf, "%u\n", 125000000 / period);
+}
+
+static ssize_t write_frame_rate(struct device *dev,
+				struct device_attribute *attr, const char *buf,
+				size_t count)
+{
+	struct video_device *vdev = to_video_device(dev);
+	struct mgb4_vout_dev *voutdev = video_get_drvdata(vdev);
+	unsigned long val;
+	int ret;
+
+	ret = kstrtoul(buf, 10, &val);
+	if (ret)
+		return ret;
+
+	mgb4_write_reg(&voutdev->mgbdev->video, voutdev->config->regs.frame_period,
+	  125000000 / val);
+
+	return count;
+}
+
+static ssize_t read_hsync_width(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	struct video_device *vdev = to_video_device(dev);
+	struct mgb4_vout_dev *voutdev = video_get_drvdata(vdev);
+	u32 sig = mgb4_read_reg(&voutdev->mgbdev->video, voutdev->config->regs.hsync);
+
+	return sprintf(buf, "%u\n", (sig & 0x00FF0000) >> 16);
+}
+
+static ssize_t write_hsync_width(struct device *dev,
+				 struct device_attribute *attr, const char *buf,
+				 size_t count)
+{
+	struct video_device *vdev = to_video_device(dev);
+	struct mgb4_vout_dev *voutdev = video_get_drvdata(vdev);
+	unsigned long val;
+	int ret;
+
+	ret = kstrtoul(buf, 10, &val);
+	if (ret)
+		return ret;
+	if (val > 0xFF)
+		return -EINVAL;
+
+	mgb4_mask_reg(&voutdev->mgbdev->video, voutdev->config->regs.hsync,
+	  0x00FF0000, val << 16);
+
+	return count;
+}
+
+static ssize_t read_vsync_width(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	struct video_device *vdev = to_video_device(dev);
+	struct mgb4_vout_dev *voutdev = video_get_drvdata(vdev);
+	u32 sig = mgb4_read_reg(&voutdev->mgbdev->video, voutdev->config->regs.vsync);
+
+	return sprintf(buf, "%u\n", (sig & 0x00FF0000) >> 16);
+}
+
+static ssize_t write_vsync_width(struct device *dev,
+				 struct device_attribute *attr, const char *buf,
+				 size_t count)
+{
+	struct video_device *vdev = to_video_device(dev);
+	struct mgb4_vout_dev *voutdev = video_get_drvdata(vdev);
+	unsigned long val;
+	int ret;
+
+	ret = kstrtoul(buf, 10, &val);
+	if (ret)
+		return ret;
+	if (val > 0xFF)
+		return -EINVAL;
+
+	mgb4_mask_reg(&voutdev->mgbdev->video, voutdev->config->regs.vsync,
+	  0x00FF0000, val << 16);
+
+	return count;
+}
+
+static ssize_t read_hback_porch(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	struct video_device *vdev = to_video_device(dev);
+	struct mgb4_vout_dev *voutdev = video_get_drvdata(vdev);
+	u32 sig = mgb4_read_reg(&voutdev->mgbdev->video, voutdev->config->regs.hsync);
+
+	return sprintf(buf, "%u\n", (sig & 0x0000FF00) >> 8);
+}
+
+static ssize_t write_hback_porch(struct device *dev,
+				 struct device_attribute *attr, const char *buf,
+				 size_t count)
+{
+	struct video_device *vdev = to_video_device(dev);
+	struct mgb4_vout_dev *voutdev = video_get_drvdata(vdev);
+	unsigned long val;
+	int ret;
+
+	ret = kstrtoul(buf, 10, &val);
+	if (ret)
+		return ret;
+	if (val > 0xFF)
+		return -EINVAL;
+
+	mgb4_mask_reg(&voutdev->mgbdev->video, voutdev->config->regs.hsync,
+	  0x0000FF00, val << 8);
+
+	return count;
+}
+
+static ssize_t read_vback_porch(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	struct video_device *vdev = to_video_device(dev);
+	struct mgb4_vout_dev *voutdev = video_get_drvdata(vdev);
+	u32 sig = mgb4_read_reg(&voutdev->mgbdev->video, voutdev->config->regs.vsync);
+
+	return sprintf(buf, "%u\n", (sig & 0x0000FF00) >> 8);
+}
+
+static ssize_t write_vback_porch(struct device *dev,
+				 struct device_attribute *attr, const char *buf,
+				 size_t count)
+{
+	struct video_device *vdev = to_video_device(dev);
+	struct mgb4_vout_dev *voutdev = video_get_drvdata(vdev);
+	unsigned long val;
+	int ret;
+
+	ret = kstrtoul(buf, 10, &val);
+	if (ret)
+		return ret;
+	if (val > 0xFF)
+		return -EINVAL;
+
+	mgb4_mask_reg(&voutdev->mgbdev->video, voutdev->config->regs.vsync,
+	  0x0000FF00, val << 8);
+
+	return count;
+}
+
+static ssize_t read_hfront_porch(struct device *dev,
+				 struct device_attribute *attr, char *buf)
+{
+	struct video_device *vdev = to_video_device(dev);
+	struct mgb4_vout_dev *voutdev = video_get_drvdata(vdev);
+	u32 sig = mgb4_read_reg(&voutdev->mgbdev->video, voutdev->config->regs.hsync);
+
+	return sprintf(buf, "%u\n", (sig & 0x000000FF));
+}
+
+static ssize_t write_hfront_porch(struct device *dev,
+				  struct device_attribute *attr,
+				  const char *buf, size_t count)
+{
+	struct video_device *vdev = to_video_device(dev);
+	struct mgb4_vout_dev *voutdev = video_get_drvdata(vdev);
+	unsigned long val;
+	int ret;
+
+	ret = kstrtoul(buf, 10, &val);
+	if (ret)
+		return ret;
+	if (val > 0xFF)
+		return -EINVAL;
+
+	mgb4_mask_reg(&voutdev->mgbdev->video, voutdev->config->regs.hsync,
+	  0x000000FF, val);
+
+	return count;
+}
+
+static ssize_t read_vfront_porch(struct device *dev,
+				 struct device_attribute *attr, char *buf)
+{
+	struct video_device *vdev = to_video_device(dev);
+	struct mgb4_vout_dev *voutdev = video_get_drvdata(vdev);
+	u32 sig = mgb4_read_reg(&voutdev->mgbdev->video, voutdev->config->regs.vsync);
+
+	return sprintf(buf, "%u\n", (sig & 0x000000FF));
+}
+
+static ssize_t write_vfront_porch(struct device *dev,
+				  struct device_attribute *attr, const char *buf,
+				  size_t count)
+{
+	struct video_device *vdev = to_video_device(dev);
+	struct mgb4_vout_dev *voutdev = video_get_drvdata(vdev);
+	unsigned long val;
+	int ret;
+
+	ret = kstrtoul(buf, 10, &val);
+	if (ret)
+		return ret;
+	if (val > 0xFF)
+		return -EINVAL;
+
+	mgb4_mask_reg(&voutdev->mgbdev->video, voutdev->config->regs.vsync,
+	  0x000000FF, val);
+
+	return count;
+}
+
+static ssize_t read_alignment(struct device *dev,
+			      struct device_attribute *attr, char *buf)
+{
+	struct video_device *vdev = to_video_device(dev);
+	struct mgb4_vout_dev *voutdev = video_get_drvdata(vdev);
+
+	return sprintf(buf, "%d\n", voutdev->alignment);
+}
+
+static ssize_t write_alignment(struct device *dev,
+			       struct device_attribute *attr, const char *buf,
+			       size_t count)
+{
+	struct video_device *vdev = to_video_device(dev);
+	struct mgb4_vout_dev *voutdev = video_get_drvdata(vdev);
+	unsigned long val, flags;
+	u32 config;
+	int ret;
+
+	ret = kstrtoul(buf, 10, &val);
+	if (ret)
+		return ret;
+	if (!val || (val & (val - 1)))
+		return -EINVAL;
+
+	spin_lock_irqsave(&(voutdev->vdev.fh_lock), flags);
+	if (!list_empty(&(voutdev->vdev.fh_list))) {
+		spin_unlock_irqrestore(&(voutdev->vdev.fh_lock), flags);
+		return -EBUSY;
+	}
+	/* Do not allow the change if loopback is active */
+	config = mgb4_read_reg(&voutdev->mgbdev->video, voutdev->config->regs.config);
+	if (((config & 0xc) >> 2) < 2) {
+		spin_unlock_irqrestore(&(voutdev->vdev.fh_lock), flags);
+		return -EPERM;
+	}
+
+	voutdev->alignment = val;
+
+	spin_unlock_irqrestore(&(voutdev->vdev.fh_lock), flags);
+
+	return count;
+}
+
+/* FPDL3 only */
+
+static ssize_t read_hsync_polarity(struct device *dev,
+				   struct device_attribute *attr, char *buf)
+{
+	struct video_device *vdev = to_video_device(dev);
+	struct mgb4_vout_dev *voutdev = video_get_drvdata(vdev);
+	u32 config = mgb4_read_reg(&voutdev->mgbdev->video,
+	  voutdev->config->regs.hsync);
+
+	return sprintf(buf, "%u\n", (config & (1<<31)) >> 31);
+}
+
+static ssize_t write_hsync_polarity(struct device *dev,
+				    struct device_attribute *attr,
+				    const char *buf, size_t count)
+{
+	struct video_device *vdev = to_video_device(dev);
+	struct mgb4_vout_dev *voutdev = video_get_drvdata(vdev);
+	unsigned long val;
+	int ret;
+
+	ret = kstrtoul(buf, 10, &val);
+	if (ret)
+		return ret;
+	if (val > 1)
+		return -EINVAL;
+
+	mgb4_mask_reg(&voutdev->mgbdev->video, voutdev->config->regs.hsync, (1<<31),
+	  val << 31);
+
+	return count;
+}
+
+static ssize_t read_vsync_polarity(struct device *dev,
+				   struct device_attribute *attr, char *buf)
+{
+	struct video_device *vdev = to_video_device(dev);
+	struct mgb4_vout_dev *voutdev = video_get_drvdata(vdev);
+	u32 config = mgb4_read_reg(&voutdev->mgbdev->video,
+	  voutdev->config->regs.vsync);
+
+	return sprintf(buf, "%u\n", (config & (1<<31)) >> 31);
+}
+
+static ssize_t write_vsync_polarity(struct device *dev,
+				    struct device_attribute *attr,
+				    const char *buf, size_t count)
+{
+	struct video_device *vdev = to_video_device(dev);
+	struct mgb4_vout_dev *voutdev = video_get_drvdata(vdev);
+	unsigned long val;
+	int ret;
+
+	ret = kstrtoul(buf, 10, &val);
+	if (ret)
+		return ret;
+	if (val > 1)
+		return -EINVAL;
+
+	mgb4_mask_reg(&voutdev->mgbdev->video, voutdev->config->regs.vsync, (1<<31),
+	  val << 31);
+
+	return count;
+}
+
+static ssize_t read_de_polarity(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	struct video_device *vdev = to_video_device(dev);
+	struct mgb4_vout_dev *voutdev = video_get_drvdata(vdev);
+	u32 config = mgb4_read_reg(&voutdev->mgbdev->video,
+	  voutdev->config->regs.vsync);
+
+	return sprintf(buf, "%u\n", (config & (1<<30)) >> 30);
+}
+
+static ssize_t write_de_polarity(struct device *dev,
+				 struct device_attribute *attr, const char *buf,
+				 size_t count)
+{
+	struct video_device *vdev = to_video_device(dev);
+	struct mgb4_vout_dev *voutdev = video_get_drvdata(vdev);
+	unsigned long val;
+	int ret;
+
+	ret = kstrtoul(buf, 10, &val);
+	if (ret)
+		return ret;
+	if (val > 1)
+		return -EINVAL;
+
+	mgb4_mask_reg(&voutdev->mgbdev->video, voutdev->config->regs.vsync, (1<<30),
+	  val << 30);
+
+	return count;
+}
+
+static ssize_t read_fpdl3_output_width(struct device *dev,
+				       struct device_attribute *attr, char *buf)
+{
+	struct video_device *vdev = to_video_device(dev);
+	struct mgb4_vout_dev *voutdev = video_get_drvdata(vdev);
+	s32 ret;
+
+	mutex_lock(&voutdev->mgbdev->i2c_lock);
+	ret = mgb4_i2c_read_byte(&voutdev->ser, 0x5B);
+	mutex_unlock(&voutdev->mgbdev->i2c_lock);
+	if (ret < 0)
+		return -EIO;
+
+	switch ((u8)ret & 0x03) {
+	case 0:
+		return sprintf(buf, "0\n");
+	case 1:
+		return sprintf(buf, "1\n");
+	case 3:
+		return sprintf(buf, "2\n");
+	default:
+		return -EINVAL;
+	}
+}
+
+static ssize_t write_fpdl3_output_width(struct device *dev,
+					struct device_attribute *attr,
+					const char *buf, size_t count)
+{
+	struct video_device *vdev = to_video_device(dev);
+	struct mgb4_vout_dev *voutdev = video_get_drvdata(vdev);
+	u8 i2c_data;
+	unsigned long val;
+	int ret;
+
+	ret = kstrtoul(buf, 10, &val);
+	if (ret)
+		return ret;
+
+	switch (val) {
+	case 0: /* auto */
+		i2c_data = 0x00;
+		break;
+	case 1: /* single */
+		i2c_data = 0x01;
+		break;
+	case 2: /* dual */
+		i2c_data = 0x03;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	mutex_lock(&voutdev->mgbdev->i2c_lock);
+	ret = mgb4_i2c_mask_byte(&voutdev->ser, 0x5B, 0x03, i2c_data);
+	mutex_unlock(&voutdev->mgbdev->i2c_lock);
+	if (ret < 0)
+		return -EIO;
+
+	return count;
+}
+
+static ssize_t read_pclk_frequency(struct device *dev,
+				   struct device_attribute *attr, char *buf)
+{
+	struct video_device *vdev = to_video_device(dev);
+	struct mgb4_vout_dev *voutdev = video_get_drvdata(vdev);
+
+	return sprintf(buf, "%u\n", voutdev->freq);
+}
+
+static ssize_t write_pclk_frequency(struct device *dev,
+				    struct device_attribute *attr,
+				    const char *buf, size_t count)
+{
+	struct video_device *vdev = to_video_device(dev);
+	struct mgb4_vout_dev *voutdev = video_get_drvdata(vdev);
+	unsigned long val, flags;
+	int ret, busy;
+	unsigned int dp;
+
+	ret = kstrtoul(buf, 10, &val);
+	if (ret)
+		return ret;
+
+	spin_lock_irqsave(&(voutdev->vdev.fh_lock), flags);
+	busy = !list_empty(&(voutdev->vdev.fh_list));
+	if (busy) {
+		spin_unlock_irqrestore(&(voutdev->vdev.fh_lock), flags);
+		return -EBUSY;
+	}
+
+	dp = (val > 50000) ? 1 : 0;
+	voutdev->freq = mgb4_cmt_set_vout(voutdev, val >> dp) << dp;
+
+	spin_unlock_irqrestore(&(voutdev->vdev.fh_lock), flags);
+
+	mgb4_mask_reg(&voutdev->mgbdev->video, voutdev->config->regs.config,
+	  0x10, dp << 4);
+	mutex_lock(&voutdev->mgbdev->i2c_lock);
+	ret = mgb4_i2c_mask_byte(&voutdev->ser, 0x4F, 1<<6, ((~dp)&1)<<6);
+	mutex_unlock(&voutdev->mgbdev->i2c_lock);
+	if (ret < 0)
+		return -EIO;
+
+	return count;
+}
+
+
+static DEVICE_ATTR(output_id, 0444, read_output_id, NULL);
+static DEVICE_ATTR(video_source, 0644, read_video_source, write_video_source);
+static DEVICE_ATTR(display_width, 0644, read_display_width,
+		   write_display_width);
+static DEVICE_ATTR(display_height, 0644, read_display_height,
+		   write_display_height);
+static DEVICE_ATTR(frame_rate, 0644, read_frame_rate, write_frame_rate);
+static DEVICE_ATTR(hsync_polarity, 0644, read_hsync_polarity,
+		   write_hsync_polarity);
+static DEVICE_ATTR(vsync_polarity, 0644, read_vsync_polarity,
+		   write_vsync_polarity);
+static DEVICE_ATTR(de_polarity, 0644, read_de_polarity,
+		   write_de_polarity);
+static DEVICE_ATTR(pclk_frequency, 0644, read_pclk_frequency,
+		   write_pclk_frequency);
+static DEVICE_ATTR(hsync_width, 0644, read_hsync_width, write_hsync_width);
+static DEVICE_ATTR(vsync_width, 0644, read_vsync_width, write_vsync_width);
+static DEVICE_ATTR(hback_porch, 0644, read_hback_porch, write_hback_porch);
+static DEVICE_ATTR(hfront_porch, 0644, read_hfront_porch, write_hfront_porch);
+static DEVICE_ATTR(vback_porch, 0644, read_vback_porch, write_vback_porch);
+static DEVICE_ATTR(vfront_porch, 0644, read_vfront_porch, write_vfront_porch);
+static DEVICE_ATTR(alignment, 0644, read_alignment, write_alignment);
+
+static DEVICE_ATTR(fpdl3_output_width, 0644, read_fpdl3_output_width,
+		   write_fpdl3_output_width);
+
+
+struct device_attribute *mgb4_fpdl3_out_attrs[] = {
+	&dev_attr_output_id,
+	&dev_attr_video_source,
+	&dev_attr_display_width,
+	&dev_attr_display_height,
+	&dev_attr_frame_rate,
+	&dev_attr_hsync_polarity,
+	&dev_attr_vsync_polarity,
+	&dev_attr_de_polarity,
+	&dev_attr_pclk_frequency,
+	&dev_attr_hsync_width,
+	&dev_attr_vsync_width,
+	&dev_attr_hback_porch,
+	&dev_attr_hfront_porch,
+	&dev_attr_vback_porch,
+	&dev_attr_vfront_porch,
+	&dev_attr_alignment,
+	&dev_attr_fpdl3_output_width,
+	NULL
+};
+
+struct device_attribute *mgb4_gmsl_out_attrs[] = {
+	&dev_attr_output_id,
+	&dev_attr_video_source,
+	&dev_attr_display_width,
+	&dev_attr_display_height,
+	&dev_attr_frame_rate,
+	NULL
+};
diff --git a/drivers/media/pci/mgb4/mgb4_sysfs_pci.c b/drivers/media/pci/mgb4/mgb4_sysfs_pci.c
new file mode 100644
index 000000000000..a7f59cd9cfc9
--- /dev/null
+++ b/drivers/media/pci/mgb4/mgb4_sysfs_pci.c
@@ -0,0 +1,83 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2021-2022 Digiteq Automotive
+ *     author: Martin Tuma <martin.tuma@digiteqautomotive.com>
+ */
+
+#include <linux/device.h>
+#include "mgb4_core.h"
+#include "mgb4_sysfs.h"
+
+static ssize_t read_module_version(struct device *dev,
+				   struct device_attribute *attr, char *buf)
+{
+	struct mgb4_dev *mgbdev = dev_get_drvdata(dev);
+
+	return sprintf(buf, "%u\n", mgbdev->module_version & 0x0F);
+}
+
+static ssize_t read_module_type(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	struct mgb4_dev *mgbdev = dev_get_drvdata(dev);
+
+	return sprintf(buf, "%u\n", mgbdev->module_version >> 4);
+}
+
+static ssize_t read_fw_version(struct device *dev,
+			       struct device_attribute *attr, char *buf)
+{
+	struct mgb4_dev *mgbdev = dev_get_drvdata(dev);
+	u32 config = mgb4_read_reg(&mgbdev->video, 0xC4);
+
+	return sprintf(buf, "%u\n", config & 0xFFFF);
+}
+
+static ssize_t read_fw_type(struct device *dev,
+			    struct device_attribute *attr, char *buf)
+{
+	struct mgb4_dev *mgbdev = dev_get_drvdata(dev);
+	u32 config = mgb4_read_reg(&mgbdev->video, 0xC4);
+
+	return sprintf(buf, "%u\n", config >> 24);
+}
+
+static ssize_t read_serial_number(struct device *dev,
+				  struct device_attribute *attr, char *buf)
+{
+	struct mgb4_dev *mgbdev = dev_get_drvdata(dev);
+	u32 sn = mgbdev->serial_number;
+
+	return sprintf(buf, "%03d-%03d-%03d-%03d\n", sn >> 24, (sn >> 16) & 0xFF,
+	  (sn >> 8) & 0xFF, sn & 0xFF);
+}
+
+static ssize_t read_temperature(struct device *dev,
+				struct device_attribute *attr, char *buf)
+{
+	struct mgb4_dev *mgbdev = dev_get_drvdata(dev);
+	u32 val10, val;
+
+	val  = mgb4_read_reg(&mgbdev->video, 0xD0);
+	val10 = ((((val >> 20) & 0xFFF) * 503975) - 1118822400) / 409600;
+
+	return sprintf(buf, "%u\n", ((val10 % 10) > 4)
+	  ? (val10 / 10) + 1 : val10 / 10);
+}
+
+static DEVICE_ATTR(module_version, 0444, read_module_version, NULL);
+static DEVICE_ATTR(module_type, 0444, read_module_type, NULL);
+static DEVICE_ATTR(fw_version, 0444, read_fw_version, NULL);
+static DEVICE_ATTR(fw_type, 0444, read_fw_type, NULL);
+static DEVICE_ATTR(serial_number, 0444, read_serial_number, NULL);
+static DEVICE_ATTR(temperature, 0444, read_temperature, NULL);
+
+struct device_attribute *mgb4_pci_attrs[] = {
+	&dev_attr_module_type,
+	&dev_attr_module_version,
+	&dev_attr_fw_type,
+	&dev_attr_fw_version,
+	&dev_attr_serial_number,
+	&dev_attr_temperature,
+	NULL
+};
diff --git a/drivers/media/pci/mgb4/mgb4_trigger.c b/drivers/media/pci/mgb4/mgb4_trigger.c
new file mode 100644
index 000000000000..c8f87eec768d
--- /dev/null
+++ b/drivers/media/pci/mgb4/mgb4_trigger.c
@@ -0,0 +1,202 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2021-2022 Digiteq Automotive
+ *     author: Martin Tuma <martin.tuma@digiteqautomotive.com>
+ */
+
+#include <linux/version.h>
+#include <linux/iio/iio.h>
+#include <linux/iio/buffer.h>
+#include <linux/iio/trigger.h>
+#include <linux/iio/trigger_consumer.h>
+#include <linux/iio/triggered_buffer.h>
+#include <linux/pci.h>
+#include <linux/dma/xilinx_xdma.h>
+#include "mgb4_core.h"
+#include "mgb4_trigger.h"
+
+struct trigger_data {
+	struct mgb4_dev *mgbdev;
+	struct iio_trigger *trig;
+};
+
+static int trigger_read_raw(struct iio_dev *indio_dev,
+			    struct iio_chan_spec const *chan, int *val,
+			    int *val2, long mask)
+{
+	struct trigger_data *st = iio_priv(indio_dev);
+
+	switch (mask) {
+	case IIO_CHAN_INFO_RAW:
+		if (iio_buffer_enabled(indio_dev))
+			return -EBUSY;
+		*val = mgb4_read_reg(&st->mgbdev->video, 0xA0);
+
+		return IIO_VAL_INT;
+	}
+
+	return -EINVAL;
+}
+
+static int trigger_set_state(struct iio_trigger *trig, bool state)
+{
+	struct iio_dev *indio_dev = iio_trigger_get_drvdata(trig);
+	struct trigger_data *st = iio_priv(indio_dev);
+
+	if (state)
+		xdma_irq_enable(&st->mgbdev->xdma, 1U<<11);
+	else
+		xdma_irq_disable(&st->mgbdev->xdma, 1U<<11);
+
+	return 0;
+
+}
+
+static const struct iio_trigger_ops trigger_ops = {
+	.set_trigger_state = &trigger_set_state,
+};
+
+static const struct iio_info trigger_info = {
+	.read_raw         = trigger_read_raw,
+};
+
+#define TRIGGER_CHANNEL(_si) {                    \
+	.type = IIO_ACTIVITY,                         \
+	.info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
+	.scan_index = _si,                            \
+	.scan_type = {                                \
+		.sign = 'u',                              \
+		.realbits = 32,                           \
+		.storagebits = 32,                        \
+		.shift = 0,                               \
+		.endianness = IIO_CPU                     \
+	},                                            \
+}
+
+static const struct iio_chan_spec trigger_channels[] = {
+	TRIGGER_CHANNEL(0),
+	IIO_CHAN_SOFT_TIMESTAMP(1),
+};
+
+static irqreturn_t trigger_handler(int irq, void *p)
+{
+	struct iio_poll_func *pf = p;
+	struct iio_dev *indio_dev = pf->indio_dev;
+	struct trigger_data *st = iio_priv(indio_dev);
+	struct {
+		u32 data;
+		s64 ts __aligned(8);
+	} scan;
+
+	scan.data = mgb4_read_reg(&st->mgbdev->video, 0xA0);
+	mgb4_write_reg(&st->mgbdev->video, 0xA0, scan.data);
+
+	iio_push_to_buffers_with_timestamp(indio_dev, &scan, pf->timestamp);
+	iio_trigger_notify_done(indio_dev->trig);
+
+	mgb4_write_reg(&st->mgbdev->video, 0xB4, 1U<<11);
+
+	return IRQ_HANDLED;
+}
+
+static int probe_trigger(struct iio_dev *indio_dev, int irq)
+{
+	int ret;
+	struct trigger_data *st = iio_priv(indio_dev);
+
+	st->trig = iio_trigger_alloc(&st->mgbdev->xdma.pdev->dev, "%s-dev%d",
+	  indio_dev->name, iio_device_id(indio_dev));
+	if (!st->trig)
+		return -ENOMEM;
+
+	ret = request_irq(irq, &iio_trigger_generic_data_rdy_poll,
+	 0, "mgb4-trigger", st->trig);
+	if (ret)
+		goto error_free_trig;
+
+	st->trig->ops = &trigger_ops;
+	iio_trigger_set_drvdata(st->trig, indio_dev);
+	ret = iio_trigger_register(st->trig);
+	if (ret)
+		goto error_free_irq;
+
+	indio_dev->trig = iio_trigger_get(st->trig);
+
+	return 0;
+
+error_free_irq:
+	free_irq(irq, st->trig);
+error_free_trig:
+	iio_trigger_free(st->trig);
+
+	return ret;
+}
+
+static void remove_trigger(struct iio_dev *indio_dev, int irq)
+{
+	struct trigger_data *st = iio_priv(indio_dev);
+
+	iio_trigger_unregister(st->trig);
+	free_irq(irq, st->trig);
+	iio_trigger_free(st->trig);
+}
+
+struct iio_dev *mgb4_trigger_create(struct mgb4_dev *mgbdev)
+{
+	struct iio_dev *indio_dev;
+	struct trigger_data *data;
+	struct pci_dev *pdev = mgbdev->xdma.pdev;
+	struct device *dev = &pdev->dev;
+	int rv;
+
+	indio_dev = iio_device_alloc(dev, sizeof(*data));
+	if (!indio_dev)
+		return NULL;
+
+	indio_dev->info = &trigger_info;
+	indio_dev->name = "mgb4";
+	indio_dev->modes = INDIO_DIRECT_MODE;
+	indio_dev->channels = trigger_channels;
+	indio_dev->num_channels = ARRAY_SIZE(trigger_channels);
+
+	data = iio_priv(indio_dev);
+	data->mgbdev = mgbdev;
+
+	rv = probe_trigger(indio_dev, MGB4_IRQ_BASE(pdev) + 11);
+	if (rv < 0) {
+		dev_err(dev, "iio triggered setup failed\n");
+		goto error_alloc;
+	}
+	rv = iio_triggered_buffer_setup(indio_dev, &iio_pollfunc_store_time,
+	  trigger_handler, NULL);
+	if (rv < 0) {
+		dev_err(dev, "iio triggered buffer setup failed\n");
+		goto error_trigger;
+	}
+	rv = iio_device_register(indio_dev);
+	if (rv < 0) {
+		dev_err(dev, "iio device register failed\n");
+		goto error_buffer;
+	}
+
+	return indio_dev;
+
+error_buffer:
+	iio_triggered_buffer_cleanup(indio_dev);
+error_trigger:
+	remove_trigger(indio_dev, MGB4_IRQ_BASE(pdev) + 11);
+error_alloc:
+	iio_device_free(indio_dev);
+
+	return NULL;
+}
+
+void mgb4_trigger_free(struct iio_dev *indio_dev)
+{
+	struct trigger_data *st = iio_priv(indio_dev);
+
+	iio_device_unregister(indio_dev);
+	iio_triggered_buffer_cleanup(indio_dev);
+	remove_trigger(indio_dev, MGB4_IRQ_BASE(st->mgbdev->xdma.pdev) + 11);
+	iio_device_free(indio_dev);
+}
diff --git a/drivers/media/pci/mgb4/mgb4_trigger.h b/drivers/media/pci/mgb4/mgb4_trigger.h
new file mode 100644
index 000000000000..9e6a651817d5
--- /dev/null
+++ b/drivers/media/pci/mgb4/mgb4_trigger.h
@@ -0,0 +1,8 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) 2021-2022 Digiteq Automotive
+ *     author: Martin Tuma <martin.tuma@digiteqautomotive.com>
+ */
+
+extern struct iio_dev *mgb4_trigger_create(struct mgb4_dev *mgbdev);
+extern void mgb4_trigger_free(struct iio_dev *indio_dev);
diff --git a/drivers/media/pci/mgb4/mgb4_vin.c b/drivers/media/pci/mgb4/mgb4_vin.c
new file mode 100644
index 000000000000..057ab39f509f
--- /dev/null
+++ b/drivers/media/pci/mgb4/mgb4_vin.c
@@ -0,0 +1,656 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2021-2022 Digiteq Automotive
+ *     author: Martin Tuma <martin.tuma@digiteqautomotive.com>
+ */
+
+#include <linux/pci.h>
+#include <linux/workqueue.h>
+#include <media/v4l2-ioctl.h>
+#include <media/videobuf2-v4l2.h>
+#include <media/videobuf2-dma-sg.h>
+#include <linux/dma/xilinx_xdma.h>
+#include "mgb4_core.h"
+#include "mgb4_sysfs.h"
+#include "mgb4_io.h"
+#include "mgb4_vout.h"
+#include "mgb4_vin.h"
+
+static const struct mgb4_vin_config vin_cfg[] = {
+	{0, 0, 0, 6, {0x10, 0x00, 0x04, 0x08, 0x1C, 0x14, 0x18, 0x20, 0x24, 0x28}},
+	{1, 1, 1, 7, {0x40, 0x30, 0x34, 0x38, 0x4C, 0x44, 0x48, 0x50, 0x54, 0x58}}
+};
+
+static const struct i2c_board_info fpdl3_deser_info[] = {
+	{I2C_BOARD_INFO("deserializer1", 0x36)},
+	{I2C_BOARD_INFO("deserializer2", 0x38)},
+};
+static const struct i2c_board_info gmsl_deser_info[] = {
+	{I2C_BOARD_INFO("deserializer1", 0x4C)},
+	{I2C_BOARD_INFO("deserializer2", 0x2A)},
+};
+
+static const struct mgb4_i2c_kv fpdl3_i2c[] = {
+	{0x06, 0xFF, 0x04}, {0x07, 0xFF, 0x01}, {0x45, 0xFF, 0xE8},
+	{0x49, 0xFF, 0x00}, {0x34, 0xFF, 0x00}, {0x23, 0xFF, 0x00}
+};
+
+static const struct mgb4_i2c_kv gmsl_i2c[] = {
+	{0x01, 0x03, 0x03}, {0x300, 0x0C, 0x0C}, {0x03, 0xC0, 0xC0},
+	{0x1CE, 0x0E, 0x0E}, {0x11, 0x05, 0x00}, {0x05, 0xC0, 0x40},
+	{0x307, 0x0F, 0x00}, {0xA0, 0x03, 0x00}, {0x3E0, 0x07, 0x07},
+	{0x308, 0x01, 0x01}, {0x10, 0x20, 0x20}, {0x300, 0x40, 0x40}
+};
+
+
+static struct mgb4_vout_dev *loopback_dev(struct mgb4_vin_dev *vindev, int i)
+{
+	struct mgb4_vout_dev *voutdev;
+	u32 config;
+
+	voutdev = vindev->mgbdev->vout[i];
+	if (!voutdev)
+		return NULL;
+
+	config = mgb4_read_reg(&voutdev->mgbdev->video, voutdev->config->regs.config);
+	if ((config & 0xc) >> 2 == vindev->config->id)
+		return voutdev;
+
+	return NULL;
+}
+
+static int loopback_active(struct mgb4_vin_dev *vindev)
+{
+	int i;
+
+	for (i = 0; i < MGB4_VOUT_DEVICES; i++)
+		if (loopback_dev(vindev, i))
+			return 1;
+
+	return 0;
+}
+
+static void set_loopback_padding(struct mgb4_vin_dev *vindev, u32 padding)
+{
+	struct mgb4_regs *video = &vindev->mgbdev->video;
+	struct mgb4_vout_dev *voutdev;
+	int i;
+
+	for (i = 0; i < MGB4_VOUT_DEVICES; i++) {
+		voutdev = loopback_dev(vindev, i);
+		if (voutdev)
+			mgb4_write_reg(video, voutdev->config->regs.padding, padding);
+	}
+}
+
+static void return_all_buffers(struct mgb4_vin_dev *vindev,
+			       enum vb2_buffer_state state)
+{
+	struct frame_buffer *buf, *node;
+	unsigned long flags;
+
+	spin_lock_irqsave(&vindev->qlock, flags);
+	list_for_each_entry_safe(buf, node, &vindev->buf_list, list) {
+		vb2_buffer_done(&buf->vb.vb2_buf, state);
+		list_del(&buf->list);
+	}
+	spin_unlock_irqrestore(&vindev->qlock, flags);
+}
+
+static int queue_setup(struct vb2_queue *q, unsigned int *nbuffers,
+		       unsigned int *nplanes, unsigned int sizes[],
+		       struct device *alloc_devs[])
+{
+	struct mgb4_vin_dev *vindev = vb2_get_drv_priv(q);
+	unsigned int size = BYTESPERLINE(vindev->width, vindev->alignment)
+	  * vindev->height;
+
+	if (*nbuffers < 2)
+		*nbuffers = 2;
+
+	if (*nplanes)
+		return sizes[0] < size ? -EINVAL : 0;
+	*nplanes = 1;
+	sizes[0] = size;
+
+	return 0;
+}
+
+static int buffer_init(struct vb2_buffer *vb)
+{
+	struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+	struct frame_buffer *buf = to_frame_buffer(vbuf);
+
+	INIT_LIST_HEAD(&buf->list);
+
+	return 0;
+}
+
+static int buffer_prepare(struct vb2_buffer *vb)
+{
+	struct mgb4_vin_dev *vindev = vb2_get_drv_priv(vb->vb2_queue);
+	struct device *dev = &vindev->mgbdev->xdma.pdev->dev;
+	unsigned int size = BYTESPERLINE(vindev->width, vindev->alignment)
+	  * vindev->height;
+
+	if (vb2_plane_size(vb, 0) < size) {
+		dev_err(dev, "buffer too small (%lu < %u)\n",
+			vb2_plane_size(vb, 0), size);
+		return -EINVAL;
+	}
+
+	vb2_set_plane_payload(vb, 0, size);
+
+	return 0;
+}
+
+static void buffer_queue(struct vb2_buffer *vb)
+{
+	struct mgb4_vin_dev *vindev = vb2_get_drv_priv(vb->vb2_queue);
+	struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+	struct frame_buffer *buf = to_frame_buffer(vbuf);
+	unsigned long flags;
+
+	spin_lock_irqsave(&vindev->qlock, flags);
+	list_add_tail(&buf->list, &vindev->buf_list);
+	spin_unlock_irqrestore(&vindev->qlock, flags);
+}
+
+static void stop_streaming(struct vb2_queue *vq)
+{
+	struct mgb4_vin_dev *vindev = vb2_get_drv_priv(vq);
+
+	xdma_irq_disable(&vindev->mgbdev->xdma, 1U<<vindev->config->vin_irq);
+
+	if (!loopback_active(vindev))
+		mgb4_mask_reg(&vindev->mgbdev->video, vindev->config->regs.config,
+		  0x2, 0x0);
+
+	cancel_work_sync(&vindev->dma_work);
+	return_all_buffers(vindev, VB2_BUF_STATE_ERROR);
+}
+
+static int start_streaming(struct vb2_queue *vq, unsigned int count)
+{
+	struct mgb4_vin_dev *vindev = vb2_get_drv_priv(vq);
+	u32 resolution = mgb4_read_reg(&vindev->mgbdev->video,
+	  vindev->config->regs.resolution);
+
+	if ((vindev->width != (resolution >> 16))
+	  || (vindev->height != (resolution & 0xFFFF))) {
+		return_all_buffers(vindev, VB2_BUF_STATE_ERROR);
+		return -EIO;
+	}
+
+	vindev->sequence = 0;
+
+	if (!loopback_active(vindev))
+		mgb4_mask_reg(&vindev->mgbdev->video, vindev->config->regs.config,
+		  0x2, 0x2);
+
+	xdma_irq_enable(&vindev->mgbdev->xdma, 1U<<vindev->config->vin_irq);
+
+	return 0;
+}
+
+static const struct vb2_ops queue_ops = {
+	.queue_setup = queue_setup,
+	.buf_init = buffer_init,
+	.buf_prepare = buffer_prepare,
+	.buf_queue = buffer_queue,
+	.start_streaming = start_streaming,
+	.stop_streaming = stop_streaming,
+	.wait_prepare = vb2_ops_wait_prepare,
+	.wait_finish = vb2_ops_wait_finish
+};
+
+static int fh_open(struct file *file)
+{
+	struct mgb4_vin_dev *vindev = video_drvdata(file);
+	struct mgb4_regs *video = &vindev->mgbdev->video;
+	u32 resolution, padding;
+	int ret;
+
+	ret = v4l2_fh_open(file);
+	if (ret)
+		return ret;
+
+	resolution = mgb4_read_reg(video, vindev->config->regs.resolution);
+	if (resolution >= ERR_NO_REG)
+		goto error;
+
+	vindev->width = resolution >> 16;
+	vindev->height = resolution & 0xFFFF;
+	if (!vindev->width || !vindev->height)
+		goto error;
+
+	xdma_irq_enable(&vindev->mgbdev->xdma, 1U<<vindev->config->err_irq);
+
+	vindev->period = mgb4_read_reg(video, vindev->config->regs.frame_period);
+
+	padding = PADDING(vindev->width, vindev->alignment);
+	mgb4_write_reg(video, vindev->config->regs.padding, padding);
+	set_loopback_padding(vindev, padding);
+
+	return 0;
+
+error:
+	v4l2_fh_release(file);
+	return -EIO;
+}
+
+static int fh_release(struct file *file)
+{
+	struct mgb4_vin_dev *vindev = video_drvdata(file);
+	struct mgb4_regs *video = &vindev->mgbdev->video;
+
+	xdma_irq_disable(&vindev->mgbdev->xdma, 1U<<vindev->config->err_irq);
+
+	mgb4_write_reg(video, vindev->config->regs.padding, 0);
+	set_loopback_padding(vindev, 0);
+
+	return vb2_fop_release(file);
+}
+
+static const struct v4l2_file_operations video_fops = {
+	.owner = THIS_MODULE,
+	.open = fh_open,
+	.release = fh_release,
+	.unlocked_ioctl = video_ioctl2,
+	.read = vb2_fop_read,
+	.mmap = vb2_fop_mmap,
+	.poll = vb2_fop_poll,
+};
+
+static int vidioc_querycap(struct file *file, void *priv,
+			   struct v4l2_capability *cap)
+{
+	struct mgb4_vin_dev *vindev = video_drvdata(file);
+
+	strscpy(cap->driver, KBUILD_MODNAME, sizeof(cap->driver));
+	strscpy(cap->card, "MGB4 PCIe Card", sizeof(cap->card));
+	snprintf(cap->bus_info, sizeof(cap->bus_info), "PCI:%s",
+	  pci_name(vindev->mgbdev->xdma.pdev));
+
+	return 0;
+}
+
+static int vidioc_enum_fmt(struct file *file, void *priv,
+			   struct v4l2_fmtdesc *f)
+{
+	if (f->index != 0)
+		return -EINVAL;
+
+	f->pixelformat = V4L2_PIX_FMT_ABGR32;
+
+	return 0;
+}
+
+static int vidioc_enum_frameintervals(struct file *file, void *priv,
+				      struct v4l2_frmivalenum *ival)
+{
+	struct mgb4_vin_dev *vindev = video_drvdata(file);
+
+	if (ival->index != 0)
+		return -EINVAL;
+	if (ival->pixel_format != V4L2_PIX_FMT_ABGR32)
+		return -EINVAL;
+	if (ival->width != vindev->width || ival->height != vindev->height)
+		return -EINVAL;
+
+	ival->type = V4L2_FRMIVAL_TYPE_DISCRETE;
+	ival->discrete.numerator = vindev->period;
+	ival->discrete.denominator = 125000000;
+
+	return 0;
+}
+
+static int vidioc_fmt(struct file *file, void *priv, struct v4l2_format *f)
+{
+	struct mgb4_vin_dev *vindev = video_drvdata(file);
+
+	f->fmt.pix.pixelformat = V4L2_PIX_FMT_ABGR32;
+	f->fmt.pix.width = vindev->width;
+	f->fmt.pix.height = vindev->height;
+	f->fmt.pix.field = V4L2_FIELD_NONE;
+	f->fmt.pix.colorspace = V4L2_COLORSPACE_RAW;
+	f->fmt.pix.bytesperline = BYTESPERLINE(vindev->width, vindev->alignment);
+	f->fmt.pix.sizeimage = f->fmt.pix.bytesperline * f->fmt.pix.height;
+
+	return 0;
+}
+
+static int vidioc_enum_input(struct file *file, void *priv,
+			     struct v4l2_input *i)
+{
+	if (i->index != 0)
+		return -EINVAL;
+
+	i->type = V4L2_INPUT_TYPE_CAMERA;
+	strscpy(i->name, "MGB4", sizeof(i->name));
+
+	return 0;
+}
+
+static int vidioc_enum_framesizes(struct file *file, void *fh,
+				  struct v4l2_frmsizeenum *fsize)
+{
+	struct mgb4_vin_dev *vindev = video_drvdata(file);
+
+	if (fsize->index != 0 || fsize->pixel_format != V4L2_PIX_FMT_ABGR32)
+		return -EINVAL;
+
+	fsize->discrete.width = vindev->width;
+	fsize->discrete.height = vindev->height;
+	fsize->type = V4L2_FRMSIZE_TYPE_DISCRETE;
+
+	return 0;
+}
+
+static int vidioc_s_input(struct file *file, void *priv, unsigned int i)
+{
+	return (i == 0) ? 0 : -EINVAL;
+}
+
+static int vidioc_g_input(struct file *file, void *priv, unsigned int *i)
+{
+	*i = 0;
+	return 0;
+}
+
+static int vidioc_queryctrl(struct file *file, void *priv,
+			    struct v4l2_queryctrl *qc)
+{
+	return -EINVAL;
+}
+
+static int vidioc_parm(struct file *file, void *priv,
+			 struct v4l2_streamparm *parm)
+{
+	struct mgb4_vin_dev *vindev = video_drvdata(file);
+	struct v4l2_fract timeperframe = {
+		.numerator = vindev->period,
+		.denominator = 125000000,
+	};
+
+	if (parm->type != V4L2_BUF_TYPE_VIDEO_CAPTURE)
+		return -EINVAL;
+
+	parm->parm.capture.readbuffers = 2;
+	parm->parm.capture.capability = V4L2_CAP_TIMEPERFRAME;
+	parm->parm.capture.timeperframe = timeperframe;
+
+	return 0;
+}
+
+static const struct v4l2_ioctl_ops video_ioctl_ops = {
+	.vidioc_querycap = vidioc_querycap,
+	.vidioc_enum_fmt_vid_cap = vidioc_enum_fmt,
+	.vidioc_try_fmt_vid_cap = vidioc_fmt,
+	.vidioc_s_fmt_vid_cap = vidioc_fmt,
+	.vidioc_g_fmt_vid_cap = vidioc_fmt,
+	.vidioc_enum_framesizes = vidioc_enum_framesizes,
+	.vidioc_enum_frameintervals = vidioc_enum_frameintervals,
+	.vidioc_enum_input = vidioc_enum_input,
+	.vidioc_g_input = vidioc_g_input,
+	.vidioc_s_input = vidioc_s_input,
+	.vidioc_queryctrl = vidioc_queryctrl,
+	.vidioc_reqbufs = vb2_ioctl_reqbufs,
+	.vidioc_create_bufs = vb2_ioctl_create_bufs,
+	.vidioc_querybuf = vb2_ioctl_querybuf,
+	.vidioc_qbuf = vb2_ioctl_qbuf,
+	.vidioc_dqbuf = vb2_ioctl_dqbuf,
+	.vidioc_expbuf = vb2_ioctl_expbuf,
+	.vidioc_streamon = vb2_ioctl_streamon,
+	.vidioc_streamoff = vb2_ioctl_streamoff,
+	.vidioc_g_parm = vidioc_parm,
+	.vidioc_s_parm = vidioc_parm
+};
+
+
+static void dma_transfer(struct work_struct *work)
+{
+	struct mgb4_vin_dev *vindev = container_of(work, struct mgb4_vin_dev, dma_work);
+	struct device *dev = &vindev->mgbdev->xdma.pdev->dev;
+	struct frame_buffer *buf = 0;
+	unsigned long flags;
+	u32 addr;
+
+
+	spin_lock_irqsave(&vindev->qlock, flags);
+	if (!list_empty(&vindev->buf_list)) {
+		buf = list_first_entry(&vindev->buf_list, struct frame_buffer, list);
+		list_del_init(vindev->buf_list.next);
+	}
+	spin_unlock_irqrestore(&vindev->qlock, flags);
+
+	if (!buf)
+		return;
+
+
+	addr = mgb4_read_reg(&vindev->mgbdev->video, vindev->config->regs.address);
+	if (addr >= ERR_QUEUE_FULL) {
+		dev_warn(dev, "frame queue error (%d)\n", (int)addr);
+		vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR);
+		return;
+	}
+
+	if (xdma_transfer(&vindev->mgbdev->xdma, vindev->config->dma_channel, false,
+	  addr, vb2_dma_sg_plane_desc(&buf->vb.vb2_buf, 0), 1000) > 0) {
+		buf->vb.vb2_buf.timestamp = ktime_get_ns();
+		buf->vb.sequence = vindev->sequence++;
+		buf->vb.field = V4L2_FIELD_NONE;
+		vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_DONE);
+	} else {
+		dev_warn(dev, "DMA transfer error\n");
+		vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR);
+	}
+}
+
+static irqreturn_t vin_handler(int irq, void *ctx)
+{
+	struct mgb4_vin_dev *vindev = (struct mgb4_vin_dev *)ctx;
+
+	schedule_work(&vindev->dma_work);
+
+	mgb4_write_reg(&vindev->mgbdev->video, 0xB4, 1U<<vindev->config->vin_irq);
+
+	return IRQ_HANDLED;
+}
+
+static irqreturn_t err_handler(int irq, void *ctx)
+{
+	struct mgb4_vin_dev *vindev = (struct mgb4_vin_dev *)ctx;
+	struct device *dev = &vindev->mgbdev->xdma.pdev->dev;
+	struct vb2_queue *vq = &(vindev->queue);
+
+	u32 resolution = mgb4_read_reg(&vindev->mgbdev->video,
+	  vindev->config->regs.resolution);
+
+	if ((vindev->width != (resolution >> 16))
+	  || (vindev->height != (resolution & 0xFFFF))) {
+		dev_warn(dev, "stream changed (%ux%u -> %ux%u)\n",
+			 vindev->width, vindev->height, resolution>>16,
+			 resolution & 0xFFFF);
+
+		if (vb2_is_streaming(vq)) {
+			return_all_buffers(vindev, VB2_BUF_STATE_ERROR);
+			vb2_queue_error(vq);
+		}
+	}
+
+	mgb4_write_reg(&vindev->mgbdev->video, 0xB4, 1U<<vindev->config->err_irq);
+
+	return IRQ_HANDLED;
+}
+
+static int deser_init(struct mgb4_vin_dev *vindev, int id)
+{
+	int rv, addr_size;
+	size_t values_count;
+	const struct mgb4_i2c_kv *values;
+	const struct i2c_board_info *info;
+	struct device *dev = &vindev->mgbdev->xdma.pdev->dev;
+
+
+	if (MGB4_IS_GMSL(vindev->mgbdev)) {
+		info = &gmsl_deser_info[id];
+		addr_size = 16;
+		values = gmsl_i2c;
+		values_count = ARRAY_SIZE(gmsl_i2c);
+	} else {
+		info = &fpdl3_deser_info[id];
+		addr_size = 8;
+		values = fpdl3_i2c;
+		values_count = ARRAY_SIZE(fpdl3_i2c);
+	}
+
+	rv = mgb4_i2c_init(&vindev->deser, vindev->mgbdev->i2c_adap, info, addr_size);
+	if (rv < 0) {
+		dev_err(dev, "failed to create deserializer\n");
+		return rv;
+	}
+	rv = mgb4_i2c_configure(&vindev->deser, values, values_count);
+	if (rv < 0) {
+		dev_err(dev, "failed to configure deserializer\n");
+		goto err_i2c_dev;
+	}
+
+	return 0;
+
+err_i2c_dev:
+	mgb4_i2c_free(&vindev->deser);
+
+	return rv;
+}
+
+struct mgb4_vin_dev *mgb4_vin_create(struct mgb4_dev *mgbdev, int id)
+{
+	int rv;
+	struct device_attribute **attr, **module_attr;
+	struct mgb4_vin_dev *vindev;
+	struct pci_dev *pdev = mgbdev->xdma.pdev;
+	struct device *dev = &pdev->dev;
+	int base_irq = MGB4_IRQ_BASE(pdev);
+
+	vindev = kzalloc(sizeof(struct mgb4_vin_dev), GFP_KERNEL);
+	if (!vindev)
+		return NULL;
+
+	vindev->mgbdev = mgbdev;
+	vindev->config = &(vin_cfg[id]);
+
+	/* Frame queue*/
+	INIT_LIST_HEAD(&vindev->buf_list);
+	spin_lock_init(&vindev->qlock);
+
+	/* DMA transfer stuff */
+	INIT_WORK(&vindev->dma_work, dma_transfer);
+
+	/* IRQ callback */
+	rv = request_irq(base_irq + vindev->config->vin_irq, vin_handler, 0,
+	  "mgb4-vin", vindev);
+	if (rv) {
+		dev_err(dev, "failed to register vin irq handler\n");
+		goto err_alloc;
+	}
+	/* Error IRQ callback */
+	rv = request_irq(base_irq + vindev->config->err_irq, err_handler, 0,
+	  "mgb4-err", vindev);
+	if (rv) {
+		dev_err(dev, "failed to register err irq handler\n");
+		goto err_vin_irq;
+	}
+
+	/* V4L2 */
+	rv = v4l2_device_register(dev, &vindev->v4l2dev);
+	if (rv) {
+		dev_err(dev, "failed to register v4l2 device\n");
+		goto err_err_irq;
+	}
+
+	mutex_init(&vindev->lock);
+
+	vindev->queue.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+	vindev->queue.io_modes = VB2_MMAP | VB2_USERPTR | VB2_DMABUF | VB2_READ;
+	vindev->queue.buf_struct_size = sizeof(struct frame_buffer);
+	vindev->queue.ops = &queue_ops;
+	vindev->queue.mem_ops = &vb2_dma_sg_memops;
+	vindev->queue.gfp_flags = GFP_DMA32;
+	vindev->queue.timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
+	vindev->queue.min_buffers_needed = 2;
+	vindev->queue.drv_priv = vindev;
+	vindev->queue.lock = &vindev->lock;
+	vindev->queue.dev = dev;
+	rv = vb2_queue_init(&vindev->queue);
+	if (rv) {
+		dev_err(dev, "failed to initialize vb2 queue\n");
+		goto err_v4l2_dev;
+	}
+
+	snprintf(vindev->vdev.name, sizeof(vindev->vdev.name), "mgb4-in%d", id+1);
+	vindev->vdev.device_caps = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_READWRITE
+	  | V4L2_CAP_STREAMING;
+	vindev->vdev.fops = &video_fops;
+	vindev->vdev.ioctl_ops = &video_ioctl_ops;
+	vindev->vdev.release = video_device_release_empty;
+	vindev->vdev.v4l2_dev = &vindev->v4l2dev;
+	vindev->vdev.lock = &vindev->lock;
+	vindev->vdev.queue = &vindev->queue;
+	video_set_drvdata(&vindev->vdev, vindev);
+
+	rv = video_register_device(&vindev->vdev, VFL_TYPE_GRABBER, -1);
+	if (rv) {
+		dev_err(dev, "failed to register video device\n");
+		goto err_v4l2_dev;
+	}
+
+	/* Deserializer */
+	rv = deser_init(vindev, id);
+	if (rv)
+		goto err_video_dev;
+
+	/* Set FPGA regs to comply with the deserializer state */
+	mgb4_mask_reg(&vindev->mgbdev->video, vindev->config->regs.config,
+	  1U<<9, 1U<<9);
+
+	/* Module sysfs attributes */
+	vindev->alignment = 1;
+	module_attr = MGB4_IS_GMSL(mgbdev)
+	  ? mgb4_gmsl_in_attrs : mgb4_fpdl3_in_attrs;
+	for (attr = module_attr; *attr; attr++)
+		device_create_file(&vindev->vdev.dev, *attr);
+
+	return vindev;
+
+err_video_dev:
+	video_unregister_device(&vindev->vdev);
+err_v4l2_dev:
+	v4l2_device_unregister(&vindev->v4l2dev);
+err_err_irq:
+	free_irq(base_irq + vindev->config->err_irq, vindev);
+err_vin_irq:
+	free_irq(base_irq + vindev->config->vin_irq, vindev);
+err_alloc:
+	kfree(vindev);
+
+	return NULL;
+}
+
+void mgb4_vin_free(struct mgb4_vin_dev *vindev)
+{
+	struct device_attribute **attr, **module_attr;
+	int base_irq = MGB4_IRQ_BASE(vindev->mgbdev->xdma.pdev);
+
+	free_irq(base_irq + vindev->config->err_irq, vindev);
+	free_irq(base_irq + vindev->config->vin_irq, vindev);
+
+	module_attr = MGB4_IS_GMSL(vindev->mgbdev)
+	  ? mgb4_gmsl_in_attrs : mgb4_fpdl3_in_attrs;
+	for (attr = module_attr; *attr; attr++)
+		device_remove_file(&vindev->vdev.dev, *attr);
+
+	mgb4_i2c_free(&vindev->deser);
+	video_unregister_device(&vindev->vdev);
+	v4l2_device_unregister(&vindev->v4l2dev);
+}
diff --git a/drivers/media/pci/mgb4/mgb4_vin.h b/drivers/media/pci/mgb4/mgb4_vin.h
new file mode 100644
index 000000000000..4b01b61c3f90
--- /dev/null
+++ b/drivers/media/pci/mgb4/mgb4_vin.h
@@ -0,0 +1,64 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) 2021-2022 Digiteq Automotive
+ *     author: Martin Tuma <martin.tuma@digiteqautomotive.com>
+ */
+
+#ifndef __MGB4_VIN_H__
+#define __MGB4_VIN_H__
+
+#include <media/v4l2-device.h>
+#include <media/v4l2-dev.h>
+#include <media/v4l2-ctrls.h>
+#include <media/videobuf2-core.h>
+#include "mgb4_i2c.h"
+
+struct mgb4_vin_regs {
+	u32 address;
+	u32 config;
+	u32 status;
+	u32 resolution;
+	u32 frame_period;
+	u32 sync;
+	u32 pclk;
+	u32 signal;
+	u32 signal2;
+	u32 padding;
+};
+
+struct mgb4_vin_config {
+	int id;
+	int dma_channel;
+	int vin_irq;
+	int err_irq;
+	struct mgb4_vin_regs regs;
+};
+
+struct mgb4_vin_dev {
+	struct mgb4_dev *mgbdev;
+	struct v4l2_device v4l2dev;
+	struct video_device vdev;
+	struct vb2_queue queue;
+	struct mutex lock;
+
+	spinlock_t qlock;
+	struct list_head buf_list;
+	struct work_struct dma_work;
+
+	unsigned int sequence;
+
+	u32 width;
+	u32 height;
+	u32 period;
+	u32 freq_range;
+	u32 alignment;
+
+	struct mgb4_i2c_client deser;
+
+	const struct mgb4_vin_config *config;
+};
+
+extern struct mgb4_vin_dev *mgb4_vin_create(struct mgb4_dev *mgbdev, int id);
+extern void mgb4_vin_free(struct mgb4_vin_dev *vindev);
+
+#endif
diff --git a/drivers/media/pci/mgb4/mgb4_vout.c b/drivers/media/pci/mgb4/mgb4_vout.c
new file mode 100644
index 000000000000..eb1fa693f42f
--- /dev/null
+++ b/drivers/media/pci/mgb4/mgb4_vout.c
@@ -0,0 +1,502 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (C) 2021-2022 Digiteq Automotive
+ *     author: Martin Tuma <martin.tuma@digiteqautomotive.com>
+ */
+
+#include <linux/pci.h>
+#include <media/v4l2-ioctl.h>
+#include <media/videobuf2-v4l2.h>
+#include <media/videobuf2-dma-sg.h>
+#include <linux/dma/xilinx_xdma.h>
+#include "mgb4_core.h"
+#include "mgb4_sysfs.h"
+#include "mgb4_io.h"
+#include "mgb4_vout.h"
+
+#define DEFAULT_WIDTH     1280
+#define DEFAULT_HEIGHT    640
+#define DEFAULT_PERIOD    (125000000 / 60)
+
+static const struct mgb4_vout_config vout_cfg[] = {
+	{0, 0, 8, {0x78, 0x60, 0x64, 0x68, 0x74, 0x6C, 0x70, 0x7c}},
+	{1, 1, 9, {0x98, 0x80, 0x84, 0x88, 0x94, 0x8c, 0x90, 0x9c}}
+};
+
+static const struct i2c_board_info fpdl3_ser_info[] = {
+	{I2C_BOARD_INFO("serializer1", 0x14)},
+	{I2C_BOARD_INFO("serializer2", 0x16)},
+};
+
+static const struct mgb4_i2c_kv fpdl3_i2c[] = {
+	{0x05, 0xFF, 0x04}, {0x06, 0xFF, 0x01}, {0xC2, 0xFF, 0x80}
+};
+
+static int queue_setup(struct vb2_queue *q, unsigned int *nbuffers,
+		       unsigned int *nplanes, unsigned int sizes[],
+		       struct device *alloc_devs[])
+{
+	struct mgb4_vout_dev *voutdev = vb2_get_drv_priv(q);
+	unsigned long size = BYTESPERLINE(voutdev->width, voutdev->alignment)
+	  * voutdev->height;
+
+	if (*nbuffers < 2)
+		*nbuffers = 2;
+
+	if (*nplanes)
+		return sizes[0] < size ? -EINVAL : 0;
+	*nplanes = 1;
+	sizes[0] = size;
+
+	return 0;
+}
+
+static int buffer_init(struct vb2_buffer *vb)
+{
+	struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+	struct frame_buffer *buf = to_frame_buffer(vbuf);
+
+	INIT_LIST_HEAD(&buf->list);
+
+	return 0;
+}
+
+static int buffer_prepare(struct vb2_buffer *vb)
+{
+	struct mgb4_vout_dev *voutdev = vb2_get_drv_priv(vb->vb2_queue);
+	struct device *dev = &voutdev->mgbdev->xdma.pdev->dev;
+	unsigned long size = BYTESPERLINE(voutdev->width, voutdev->alignment)
+	  * voutdev->height;
+
+	if (vb2_plane_size(vb, 0) < size) {
+		dev_err(dev, "buffer too small (%lu < %lu)\n",
+			vb2_plane_size(vb, 0), size);
+		return -EINVAL;
+	}
+
+	vb2_set_plane_payload(vb, 0, size);
+
+	return 0;
+}
+
+static void buffer_queue(struct vb2_buffer *vb)
+{
+	struct mgb4_vout_dev *vindev = vb2_get_drv_priv(vb->vb2_queue);
+	struct vb2_v4l2_buffer *vbuf = to_vb2_v4l2_buffer(vb);
+	struct frame_buffer *buf = to_frame_buffer(vbuf);
+	unsigned long flags;
+
+	spin_lock_irqsave(&vindev->qlock, flags);
+	list_add_tail(&buf->list, &vindev->buf_list);
+	spin_unlock_irqrestore(&vindev->qlock, flags);
+}
+
+static void stop_streaming(struct vb2_queue *vq)
+{
+	struct mgb4_vout_dev *voutdev = vb2_get_drv_priv(vq);
+	struct frame_buffer *buf, *node;
+	unsigned long flags;
+
+	xdma_irq_disable(&voutdev->mgbdev->xdma, 1U<<voutdev->config->irq);
+
+	cancel_work_sync(&voutdev->dma_work);
+
+	mgb4_mask_reg(&voutdev->mgbdev->video, voutdev->config->regs.config,
+	  0x2, 0x0);
+
+	spin_lock_irqsave(&voutdev->qlock, flags);
+	list_for_each_entry_safe(buf, node, &voutdev->buf_list, list) {
+		vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR);
+		list_del(&buf->list);
+	}
+	spin_unlock_irqrestore(&voutdev->qlock, flags);
+}
+
+static int start_streaming(struct vb2_queue *vq, unsigned int count)
+{
+	struct mgb4_vout_dev *voutdev = vb2_get_drv_priv(vq);
+	struct device *dev = &voutdev->mgbdev->xdma.pdev->dev;
+	struct frame_buffer *buf = 0;
+	struct mgb4_regs *video = &voutdev->mgbdev->video;
+	u32 addr;
+
+	mgb4_mask_reg(video, voutdev->config->regs.config, 0x2, 0x2);
+
+	addr = mgb4_read_reg(video, voutdev->config->regs.address);
+	if (addr >= ERR_QUEUE_FULL) {
+		dev_err(dev, "frame queue error (%d)\n", (int)addr);
+		return -EIO;
+	}
+
+	if (!list_empty(&voutdev->buf_list)) {
+		buf = list_first_entry(&voutdev->buf_list, struct frame_buffer, list);
+		list_del_init(voutdev->buf_list.next);
+	}
+	if (!buf) {
+		dev_err(dev, "empty v4l2 queue\n");
+		return -EIO;
+	}
+
+	if (xdma_transfer(&voutdev->mgbdev->xdma, voutdev->config->dma_channel,
+	  true, addr, vb2_dma_sg_plane_desc(&buf->vb.vb2_buf, 0), 1000) > 0) {
+		vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_DONE);
+	} else {
+		dev_warn(dev, "DMA transfer error\n");
+		vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR);
+	}
+
+	xdma_irq_enable(&voutdev->mgbdev->xdma, 1U<<voutdev->config->irq);
+
+	return 0;
+}
+
+static const struct vb2_ops queue_ops = {
+	.queue_setup = queue_setup,
+	.buf_init = buffer_init,
+	.buf_prepare = buffer_prepare,
+	.buf_queue = buffer_queue,
+	.start_streaming = start_streaming,
+	.stop_streaming = stop_streaming,
+	.wait_prepare = vb2_ops_wait_prepare,
+	.wait_finish = vb2_ops_wait_finish
+};
+
+
+static int vidioc_querycap(struct file *file, void *priv,
+			   struct v4l2_capability *cap)
+{
+	struct mgb4_vout_dev *voutdev = video_drvdata(file);
+
+	strscpy(cap->driver, KBUILD_MODNAME, sizeof(cap->driver));
+	strscpy(cap->card, "MGB4 PCIe Card", sizeof(cap->card));
+	snprintf(cap->bus_info, sizeof(cap->bus_info), "PCI:%s",
+	  pci_name(voutdev->mgbdev->xdma.pdev));
+
+	return 0;
+}
+
+
+static int vidioc_enum_fmt(struct file *file, void *priv,
+			   struct v4l2_fmtdesc *f)
+{
+	if (f->index != 0)
+		return -EINVAL;
+
+	f->pixelformat = V4L2_PIX_FMT_ABGR32;
+
+	return 0;
+}
+
+static int vidioc_fmt(struct file *file, void *priv, struct v4l2_format *f)
+{
+	struct mgb4_vout_dev *voutdev = video_drvdata(file);
+
+	f->fmt.pix.pixelformat = V4L2_PIX_FMT_ABGR32;
+	f->fmt.pix.width = voutdev->width;
+	f->fmt.pix.height = voutdev->height;
+	f->fmt.pix.field = V4L2_FIELD_NONE;
+	f->fmt.pix.colorspace = V4L2_COLORSPACE_RAW;
+	f->fmt.pix.bytesperline = BYTESPERLINE(voutdev->width, voutdev->alignment);
+	f->fmt.pix.sizeimage = f->fmt.pix.width * 4 * f->fmt.pix.height;
+
+	return 0;
+}
+
+static int vidioc_g_output(struct file *file, void *priv, unsigned int *i)
+{
+	*i = 0;
+	return 0;
+}
+
+static int vidioc_s_output(struct file *file, void *priv, unsigned int i)
+{
+	return i ? -EINVAL : 0;
+}
+
+static int vidioc_enum_output(struct file *file, void *priv, struct v4l2_output *out)
+{
+	if (out->index != 0)
+		return -EINVAL;
+
+	out->type = V4L2_OUTPUT_TYPE_ANALOG;
+	strscpy(out->name, "MGB4", sizeof(out->name));
+
+	return 0;
+}
+
+static int vidioc_queryctrl(struct file *file, void *priv,
+			    struct v4l2_queryctrl *qc)
+{
+	return -EINVAL;
+}
+
+static const struct v4l2_ioctl_ops video_ioctl_ops = {
+	.vidioc_querycap = vidioc_querycap,
+	.vidioc_enum_fmt_vid_out = vidioc_enum_fmt,
+	.vidioc_try_fmt_vid_out = vidioc_fmt,
+	.vidioc_s_fmt_vid_out = vidioc_fmt,
+	.vidioc_g_fmt_vid_out = vidioc_fmt,
+	.vidioc_enum_output = vidioc_enum_output,
+	.vidioc_g_output = vidioc_g_output,
+	.vidioc_s_output = vidioc_s_output,
+	.vidioc_queryctrl = vidioc_queryctrl,
+	.vidioc_reqbufs = vb2_ioctl_reqbufs,
+	.vidioc_create_bufs = vb2_ioctl_create_bufs,
+	.vidioc_querybuf = vb2_ioctl_querybuf,
+	.vidioc_qbuf = vb2_ioctl_qbuf,
+	.vidioc_dqbuf = vb2_ioctl_dqbuf,
+	.vidioc_expbuf = vb2_ioctl_expbuf,
+	.vidioc_streamon = vb2_ioctl_streamon,
+	.vidioc_streamoff = vb2_ioctl_streamoff,
+};
+
+static int fh_open(struct file *file)
+{
+	struct mgb4_vout_dev *voutdev = video_drvdata(file);
+	struct mgb4_regs *video = &voutdev->mgbdev->video;
+	u32 config, resolution;
+	int ret;
+
+	ret = v4l2_fh_open(file);
+	if (ret)
+		return ret;
+
+	config = mgb4_read_reg(video, voutdev->config->regs.config);
+	if ((config & 0xc) >> 2 != voutdev->config->id + MGB4_VIN_DEVICES)
+		goto error;
+
+	resolution = mgb4_read_reg(video, voutdev->config->regs.resolution);
+	voutdev->width = resolution >> 16;
+	voutdev->height = resolution & 0xFFFF;
+
+	mgb4_write_reg(video, voutdev->config->regs.padding,
+	  PADDING(voutdev->width, voutdev->alignment));
+
+	return 0;
+
+error:
+	v4l2_fh_release(file);
+	return -EBUSY;
+}
+
+static const struct v4l2_file_operations video_fops = {
+	.owner = THIS_MODULE,
+	.open = fh_open,
+	.release = vb2_fop_release,
+	.unlocked_ioctl = video_ioctl2,
+	.write = vb2_fop_write,
+	.mmap = vb2_fop_mmap,
+	.poll = vb2_fop_poll,
+};
+
+static void dma_transfer(struct work_struct *work)
+{
+	struct mgb4_vout_dev *voutdev = container_of(work, struct mgb4_vout_dev,
+	  dma_work);
+	struct device *dev = &voutdev->mgbdev->xdma.pdev->dev;
+	struct frame_buffer *buf = 0;
+	unsigned long flags;
+	u32 addr;
+
+	spin_lock_irqsave(&voutdev->qlock, flags);
+	if (!list_empty(&voutdev->buf_list)) {
+		buf = list_first_entry(&voutdev->buf_list, struct frame_buffer, list);
+		list_del_init(voutdev->buf_list.next);
+	}
+	spin_unlock_irqrestore(&voutdev->qlock, flags);
+
+	if (!buf)
+		return;
+
+	addr = mgb4_read_reg(&voutdev->mgbdev->video, voutdev->config->regs.address);
+	if (addr >= ERR_QUEUE_FULL) {
+		dev_warn(dev, "frame queue error (%d)\n", (int)addr);
+		vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR);
+		return;
+	}
+
+	if (xdma_transfer(&voutdev->mgbdev->xdma, voutdev->config->dma_channel,
+	  true, addr, vb2_dma_sg_plane_desc(&buf->vb.vb2_buf, 0), 1000) > 0) {
+		vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_DONE);
+	} else {
+		dev_warn(dev, "DMA transfer error\n");
+		vb2_buffer_done(&buf->vb.vb2_buf, VB2_BUF_STATE_ERROR);
+	}
+}
+
+static irqreturn_t handler(int irq, void *ctx)
+{
+	struct mgb4_vout_dev *voutdev = (struct mgb4_vout_dev *)ctx;
+
+	schedule_work(&voutdev->dma_work);
+
+	mgb4_write_reg(&voutdev->mgbdev->video, 0xB4, 1U<<voutdev->config->irq);
+
+	return IRQ_HANDLED;
+}
+
+static int ser_init(struct mgb4_vout_dev *voutdev, int id)
+{
+	int rv, addr_size;
+	size_t values_count;
+	const struct mgb4_i2c_kv *values;
+	const struct i2c_board_info *info;
+	struct device *dev = &voutdev->mgbdev->xdma.pdev->dev;
+
+
+	if (MGB4_IS_GMSL(voutdev->mgbdev))
+		return 0;
+
+	info = &fpdl3_ser_info[id];
+	addr_size = 8;
+	values = fpdl3_i2c;
+	values_count = ARRAY_SIZE(fpdl3_i2c);
+
+	rv = mgb4_i2c_init(&voutdev->ser, voutdev->mgbdev->i2c_adap, info, addr_size);
+	if (rv < 0) {
+		dev_err(dev, "failed to create serializer\n");
+		return rv;
+	}
+	rv = mgb4_i2c_configure(&voutdev->ser, values, values_count);
+	if (rv < 0) {
+		dev_err(dev, "failed to configure serializer\n");
+		goto err_i2c_dev;
+	}
+
+	return 0;
+
+err_i2c_dev:
+	mgb4_i2c_free(&voutdev->ser);
+
+	return rv;
+}
+
+struct mgb4_vout_dev *mgb4_vout_create(struct mgb4_dev *mgbdev, int id)
+{
+	int rv;
+	struct device_attribute **attr, **module_attr;
+	struct mgb4_vout_dev *voutdev;
+	struct mgb4_regs *video;
+	struct pci_dev *pdev = mgbdev->xdma.pdev;
+	struct device *dev = &pdev->dev;
+
+	voutdev = kzalloc(sizeof(struct mgb4_vout_dev), GFP_KERNEL);
+	if (!voutdev)
+		return NULL;
+
+	voutdev->mgbdev = mgbdev;
+	voutdev->config = &(vout_cfg[id]);
+	video = &voutdev->mgbdev->video;
+
+	/* Frame queue*/
+	INIT_LIST_HEAD(&voutdev->buf_list);
+	spin_lock_init(&voutdev->qlock);
+
+	/* DMA transfer stuff */
+	INIT_WORK(&voutdev->dma_work, dma_transfer);
+
+	/* IRQ callback */
+	rv = request_irq(MGB4_IRQ_BASE(pdev) + voutdev->config->irq, handler,
+			 0, "mgb4-vout", voutdev);
+	if (rv) {
+		dev_err(dev, "failed to register irq handler\n");
+		goto err_alloc;
+	}
+
+	/* V4L2 */
+	rv = v4l2_device_register(dev, &voutdev->v4l2dev);
+	if (rv) {
+		dev_err(dev, "failed to register v4l2 device\n");
+		goto err_irq;
+	}
+
+	mutex_init(&voutdev->lock);
+
+	voutdev->queue.type = V4L2_BUF_TYPE_VIDEO_OUTPUT;
+	voutdev->queue.io_modes = VB2_MMAP | VB2_USERPTR | VB2_DMABUF | VB2_WRITE;
+	voutdev->queue.buf_struct_size = sizeof(struct frame_buffer);
+	voutdev->queue.ops = &queue_ops;
+	voutdev->queue.mem_ops = &vb2_dma_sg_memops;
+	voutdev->queue.gfp_flags = GFP_DMA32;
+	voutdev->queue.timestamp_flags = V4L2_BUF_FLAG_TIMESTAMP_MONOTONIC;
+	voutdev->queue.min_buffers_needed = 2;
+	voutdev->queue.drv_priv = voutdev;
+	voutdev->queue.lock = &voutdev->lock;
+	voutdev->queue.dev = dev;
+	rv = vb2_queue_init(&voutdev->queue);
+	if (rv) {
+		dev_err(dev, "failed to initialize vb2 queue\n");
+		goto err_v4l2_dev;
+	}
+
+	snprintf(voutdev->vdev.name, sizeof(voutdev->vdev.name), "mgb4-out%d", id+1);
+	voutdev->vdev.device_caps = V4L2_CAP_VIDEO_OUTPUT | V4L2_CAP_READWRITE
+	  | V4L2_CAP_STREAMING;
+	voutdev->vdev.vfl_dir = VFL_DIR_TX;
+	voutdev->vdev.fops = &video_fops;
+	voutdev->vdev.ioctl_ops = &video_ioctl_ops;
+	voutdev->vdev.release = video_device_release_empty;
+	voutdev->vdev.v4l2_dev = &voutdev->v4l2dev;
+	voutdev->vdev.lock = &voutdev->lock;
+	voutdev->vdev.queue = &voutdev->queue;
+	video_set_drvdata(&voutdev->vdev, voutdev);
+
+	rv = video_register_device(&voutdev->vdev, VFL_TYPE_GRABBER, -1);
+	if (rv) {
+		dev_err(dev, "failed to register video device\n");
+		goto err_v4l2_dev;
+	}
+
+	/* Serializer */
+	rv = ser_init(voutdev, id);
+	if (rv)
+		goto err_video_dev;
+
+	/* Set the FPGA registers default values */
+	mgb4_mask_reg(video, voutdev->config->regs.config, 0xc,
+	  (voutdev->config->id + MGB4_VIN_DEVICES) << 2);
+	mgb4_write_reg(video, voutdev->config->regs.resolution,
+	  (DEFAULT_WIDTH << 16) | DEFAULT_HEIGHT);
+	mgb4_write_reg(video, voutdev->config->regs.frame_period,
+	  DEFAULT_PERIOD);
+
+	/* Module sysfs attributes */
+	voutdev->alignment = 1;
+	module_attr = MGB4_IS_GMSL(mgbdev)
+	  ? mgb4_gmsl_out_attrs : mgb4_fpdl3_out_attrs;
+	for (attr = module_attr; *attr; attr++)
+		device_create_file(&voutdev->vdev.dev, *attr);
+
+	/* Set the output frequency according to the FPGA defaults */
+	voutdev->freq = 70000;
+
+	return voutdev;
+
+err_video_dev:
+	video_unregister_device(&voutdev->vdev);
+err_v4l2_dev:
+	v4l2_device_unregister(&voutdev->v4l2dev);
+err_irq:
+	free_irq(MGB4_IRQ_BASE(pdev) + voutdev->config->irq, voutdev);
+err_alloc:
+	kfree(voutdev);
+
+	return NULL;
+}
+
+void mgb4_vout_free(struct mgb4_vout_dev *voutdev)
+{
+	struct device_attribute **attr, **module_attr;
+	struct pci_dev *pdev = voutdev->mgbdev->xdma.pdev;
+
+	free_irq(MGB4_IRQ_BASE(pdev) + voutdev->config->irq, voutdev);
+
+	module_attr = MGB4_IS_GMSL(voutdev->mgbdev)
+	  ? mgb4_gmsl_out_attrs : mgb4_fpdl3_out_attrs;
+	for (attr = module_attr; *attr; attr++)
+		device_remove_file(&voutdev->vdev.dev, *attr);
+
+	mgb4_i2c_free(&voutdev->ser);
+	video_unregister_device(&voutdev->vdev);
+	v4l2_device_unregister(&voutdev->v4l2dev);
+}
diff --git a/drivers/media/pci/mgb4/mgb4_vout.h b/drivers/media/pci/mgb4/mgb4_vout.h
new file mode 100644
index 000000000000..902b6b8deb21
--- /dev/null
+++ b/drivers/media/pci/mgb4/mgb4_vout.h
@@ -0,0 +1,58 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (C) 2021-2022 Digiteq Automotive
+ *     author: Martin Tuma <martin.tuma@digiteqautomotive.com>
+ */
+
+#ifndef __MGB4_VOUT_H__
+#define __MGB4_VOUT_H__
+
+#include <media/v4l2-device.h>
+#include <media/v4l2-dev.h>
+#include <media/v4l2-ctrls.h>
+#include <media/videobuf2-core.h>
+#include "mgb4_i2c.h"
+
+struct mgb4_vout_regs {
+	u32 address;
+	u32 config;
+	u32 status;
+	u32 resolution;
+	u32 frame_period;
+	u32 hsync;
+	u32 vsync;
+	u32 padding;
+};
+
+struct mgb4_vout_config {
+	int id;
+	int dma_channel;
+	int irq;
+	struct mgb4_vout_regs regs;
+};
+
+struct mgb4_vout_dev {
+	struct mgb4_dev *mgbdev;
+	struct v4l2_device v4l2dev;
+	struct video_device vdev;
+	struct vb2_queue queue;
+	struct mutex lock;
+
+	spinlock_t qlock;
+	struct list_head buf_list;
+	struct work_struct dma_work;
+
+	u32 width;
+	u32 height;
+	u32 freq;
+	u32 alignment;
+
+	struct mgb4_i2c_client ser;
+
+	const struct mgb4_vout_config *config;
+};
+
+extern struct mgb4_vout_dev *mgb4_vout_create(struct mgb4_dev *mgbdev, int id);
+extern void mgb4_vout_free(struct mgb4_vout_dev *voutdev);
+
+#endif
-- 
2.37.2


^ permalink raw reply related	[flat|nested] 13+ messages in thread

* Re: [PATCH v2 2/3] Added Xilinx XDMA IP core driver
  2022-09-19 18:55 ` [PATCH v2 2/3] Added Xilinx XDMA IP core driver tumic
@ 2022-09-20  2:12   ` kernel test robot
  2022-09-21  6:24   ` Lizhi Hou
  1 sibling, 0 replies; 13+ messages in thread
From: kernel test robot @ 2022-09-20  2:12 UTC (permalink / raw)
  To: tumic, Mauro Carvalho Chehab, Vinod Koul, Michal Simek
  Cc: kbuild-all, linux-media, linux-kernel, dmaengine, linux-i2c,
	Martin Tůma

Hi,

Thank you for the patch! Perhaps something to improve:

[auto build test WARNING on vkoul-dmaengine/next]
[also build test WARNING on linus/master v6.0-rc6 next-20220919]
[cannot apply to media-tree/master xilinx-xlnx/master]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]

url:    https://github.com/intel-lab-lkp/linux/commits/tumic-gpxsee-org/Digiteq-Automotive-MGB4-driver/20220920-021718
base:   https://git.kernel.org/pub/scm/linux/kernel/git/vkoul/dmaengine.git next
config: s390-allyesconfig (https://download.01.org/0day-ci/archive/20220920/202209201009.KI4tLr7u-lkp@intel.com/config)
compiler: s390-linux-gcc (GCC) 12.1.0
reproduce (this is a W=1 build):
        wget https://raw.githubusercontent.com/intel/lkp-tests/master/sbin/make.cross -O ~/bin/make.cross
        chmod +x ~/bin/make.cross
        # https://github.com/intel-lab-lkp/linux/commit/ad840c5e2b9ee9a8b1ceb4879fe7f82edcc767c5
        git remote add linux-review https://github.com/intel-lab-lkp/linux
        git fetch --no-tags linux-review tumic-gpxsee-org/Digiteq-Automotive-MGB4-driver/20220920-021718
        git checkout ad840c5e2b9ee9a8b1ceb4879fe7f82edcc767c5
        # save the config file
        mkdir build_dir && cp config build_dir/.config
        COMPILER_INSTALL_PATH=$HOME/0day COMPILER=gcc-12.1.0 make.cross W=1 O=build_dir ARCH=s390 SHELL=/bin/bash drivers/dma/xilinx/

If you fix the issue, kindly add following tag where applicable
Reported-by: kernel test robot <lkp@intel.com>

All warnings (new ones prefixed by >>):

   drivers/dma/xilinx/xilinx_xdma.c: In function 'read_interrupts':
>> drivers/dma/xilinx/xilinx_xdma.c:409:17: warning: variable 'hi' set but not used [-Wunused-but-set-variable]
     409 |         u32 lo, hi;
         |                 ^~
>> drivers/dma/xilinx/xilinx_xdma.c:409:13: warning: variable 'lo' set but not used [-Wunused-but-set-variable]
     409 |         u32 lo, hi;
         |             ^~


vim +/hi +409 drivers/dma/xilinx/xilinx_xdma.c

   404	
   405	static void read_interrupts(struct xdma_dev *xdev)
   406	{
   407		struct interrupt_regs *reg =
   408			(struct interrupt_regs *)(xdev->config_bar + XDMA_OFS_INT_CTRL);
 > 409		u32 lo, hi;
   410	
   411		hi = ioread32(&reg->user_int_request);
   412		lo = ioread32(&reg->channel_int_request);
   413	}
   414	

-- 
0-DAY CI Kernel Test Service
https://01.org/lkp

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH v2 1/3] Added platform module alias for the xiic I2C driver
  2022-09-19 18:55 ` [PATCH v2 1/3] Added platform module alias for the xiic I2C driver tumic
@ 2022-09-20 14:30   ` Michal Simek
  0 siblings, 0 replies; 13+ messages in thread
From: Michal Simek @ 2022-09-20 14:30 UTC (permalink / raw)
  To: tumic, Mauro Carvalho Chehab, Vinod Koul, Michal Simek, Sreeram,
	Manikanta
  Cc: linux-media, linux-kernel, dmaengine, linux-i2c, Martin Tůma



On 9/19/22 20:55, tumic@gpxsee.org wrote:
> From: Martin Tůma <martin.tuma@digiteqautomotive.com>

Subject should contain subsystem prefix and driver.

i2c: xiic: is commonly used in this driver.

> 
> The missing "platform" alias is required for the mgb4 v4l2 driver to load
> the i2c controller driver when probing the HW.
> 
> Signed-off-by: Martin Tůma <martin.tuma@digiteqautomotive.com>
> ---
>   drivers/i2c/busses/i2c-xiic.c | 1 +
>   1 file changed, 1 insertion(+)
> 
> diff --git a/drivers/i2c/busses/i2c-xiic.c b/drivers/i2c/busses/i2c-xiic.c
> index b3fe6b2aa3ca..277a02455cdd 100644
> --- a/drivers/i2c/busses/i2c-xiic.c
> +++ b/drivers/i2c/busses/i2c-xiic.c
> @@ -920,6 +920,7 @@ static struct platform_driver xiic_i2c_driver = {
> 
>   module_platform_driver(xiic_i2c_driver);
> 
> +MODULE_ALIAS("platform:" DRIVER_NAME);
>   MODULE_AUTHOR("info@mocean-labs.com");
>   MODULE_DESCRIPTION("Xilinx I2C bus driver");
>   MODULE_LICENSE("GPL v2");
> --
> 2.37.2
> 

Change is fine.

Thanks,
Michal

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH v2 0/3] Digiteq Automotive MGB4 driver
  2022-09-19 18:55 [PATCH v2 0/3] Digiteq Automotive MGB4 driver tumic
                   ` (2 preceding siblings ...)
  2022-09-19 18:55 ` [PATCH v2 3/3] Added Digiteq Automotive MGB4 driver tumic
@ 2022-09-21  5:32 ` Sonal Santan
  2022-09-21  9:44   ` Martin Tůma
  3 siblings, 1 reply; 13+ messages in thread
From: Sonal Santan @ 2022-09-21  5:32 UTC (permalink / raw)
  To: tumic, Mauro Carvalho Chehab, Vinod Koul, Michal Simek
  Cc: linux-media, linux-kernel, dmaengine, linux-i2c, Martin Tůma

On 9/19/22 11:55, tumic@gpxsee.org wrote:
> From: Martin Tůma <martin.tuma@digiteqautomotive.com>
> 
> Hi,
> This series of patches adds a driver for the Digiteq Automotive MGB4 grabber
> card. MGB4 is a modular frame grabber PCIe card for automotive video interfaces
> (FPD-Link and GMSL for now). It is based on a Xilinx FPGA and uses their
> XDMA IP core for DMA transfers. Additionally, Xilinx I2C and SPI IP cores
> which already have drivers in linux are used in the design.
> 
> Except of the required xiic driver alias, the patches are split into two parts:
> the XDMA driver and a "standard" v4l2 device driver. The XDMA driver is
> originally based on Xilinx's sample code that can be found at:
> https://github.com/Xilinx/dma_ip_drivers

Hello Martin,

Xilinx/AMD is working on upstreaming the XDMA driver into Linux 
dmaengine subsystem for use by all users of XDMA IP. You can find the V3 
patch set here:

https://lore.kernel.org/dmaengine/1663631039-49732-1-git-send-email-lizhi.hou@amd.com/T/#t

Will appreciate your review of that patch set and hopefully MGB4 driver 
can be rebased on top it?

-Sonal
> 
> The rest is a quite standard v4l2 driver, with one exception - there are
> a lot of sysfs options that may/must be set before opening the v4l2 device
> to adapt the card on a specific signal (see mgb4-sysfs.rst for details)
> as the card must be able to work with various signal sources (or displays)
> that can not be auto-detected.
> 
> I have run the driver through the v4l2-compliance test suite for both the
> input and the output and the results look fine to me (I can provide the
> output if required).
> 
> Changes in v2:
> * Completely rewritten the original Xilinx's XDMA driver to meet kernel code
>    standards.
> * Added all required "to" and "cc" mail addresses.
> 
> Martin Tůma (3):
>    Added platform module alias for the xiic I2C driver
>    Added Xilinx XDMA IP core driver
>    Added Digiteq Automotive MGB4 driver
> 
>   Documentation/admin-guide/media/mgb4-iio.rst  |   30 +
>   Documentation/admin-guide/media/mgb4-mtd.rst  |   16 +
>   .../admin-guide/media/mgb4-sysfs.rst          |  297 +++
>   drivers/dma/Kconfig                           |    7 +
>   drivers/dma/xilinx/Makefile                   |    1 +
>   drivers/dma/xilinx/xilinx_xdma.c              | 2042 +++++++++++++++++
>   drivers/i2c/busses/i2c-xiic.c                 |    1 +
>   drivers/media/pci/Kconfig                     |    1 +
>   drivers/media/pci/Makefile                    |    1 +
>   drivers/media/pci/mgb4/Kconfig                |   17 +
>   drivers/media/pci/mgb4/Makefile               |    6 +
>   drivers/media/pci/mgb4/mgb4_cmt.c             |  243 ++
>   drivers/media/pci/mgb4/mgb4_cmt.h             |   16 +
>   drivers/media/pci/mgb4/mgb4_core.c            |  554 +++++
>   drivers/media/pci/mgb4/mgb4_core.h            |   58 +
>   drivers/media/pci/mgb4/mgb4_i2c.c             |  139 ++
>   drivers/media/pci/mgb4/mgb4_i2c.h             |   35 +
>   drivers/media/pci/mgb4/mgb4_io.h              |   36 +
>   drivers/media/pci/mgb4/mgb4_regs.c            |   30 +
>   drivers/media/pci/mgb4/mgb4_regs.h            |   35 +
>   drivers/media/pci/mgb4/mgb4_sysfs.h           |   18 +
>   drivers/media/pci/mgb4/mgb4_sysfs_in.c        |  750 ++++++
>   drivers/media/pci/mgb4/mgb4_sysfs_out.c       |  734 ++++++
>   drivers/media/pci/mgb4/mgb4_sysfs_pci.c       |   83 +
>   drivers/media/pci/mgb4/mgb4_trigger.c         |  202 ++
>   drivers/media/pci/mgb4/mgb4_trigger.h         |    8 +
>   drivers/media/pci/mgb4/mgb4_vin.c             |  656 ++++++
>   drivers/media/pci/mgb4/mgb4_vin.h             |   64 +
>   drivers/media/pci/mgb4/mgb4_vout.c            |  502 ++++
>   drivers/media/pci/mgb4/mgb4_vout.h            |   58 +
>   include/linux/dma/xilinx_xdma.h               |   44 +
>   31 files changed, 6684 insertions(+)
>   create mode 100644 Documentation/admin-guide/media/mgb4-iio.rst
>   create mode 100644 Documentation/admin-guide/media/mgb4-mtd.rst
>   create mode 100644 Documentation/admin-guide/media/mgb4-sysfs.rst
>   create mode 100644 drivers/dma/xilinx/xilinx_xdma.c
>   create mode 100644 drivers/media/pci/mgb4/Kconfig
>   create mode 100644 drivers/media/pci/mgb4/Makefile
>   create mode 100644 drivers/media/pci/mgb4/mgb4_cmt.c
>   create mode 100644 drivers/media/pci/mgb4/mgb4_cmt.h
>   create mode 100644 drivers/media/pci/mgb4/mgb4_core.c
>   create mode 100644 drivers/media/pci/mgb4/mgb4_core.h
>   create mode 100644 drivers/media/pci/mgb4/mgb4_i2c.c
>   create mode 100644 drivers/media/pci/mgb4/mgb4_i2c.h
>   create mode 100644 drivers/media/pci/mgb4/mgb4_io.h
>   create mode 100644 drivers/media/pci/mgb4/mgb4_regs.c
>   create mode 100644 drivers/media/pci/mgb4/mgb4_regs.h
>   create mode 100644 drivers/media/pci/mgb4/mgb4_sysfs.h
>   create mode 100644 drivers/media/pci/mgb4/mgb4_sysfs_in.c
>   create mode 100644 drivers/media/pci/mgb4/mgb4_sysfs_out.c
>   create mode 100644 drivers/media/pci/mgb4/mgb4_sysfs_pci.c
>   create mode 100644 drivers/media/pci/mgb4/mgb4_trigger.c
>   create mode 100644 drivers/media/pci/mgb4/mgb4_trigger.h
>   create mode 100644 drivers/media/pci/mgb4/mgb4_vin.c
>   create mode 100644 drivers/media/pci/mgb4/mgb4_vin.h
>   create mode 100644 drivers/media/pci/mgb4/mgb4_vout.c
>   create mode 100644 drivers/media/pci/mgb4/mgb4_vout.h
>   create mode 100644 include/linux/dma/xilinx_xdma.h
> 


^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH v2 2/3] Added Xilinx XDMA IP core driver
  2022-09-19 18:55 ` [PATCH v2 2/3] Added Xilinx XDMA IP core driver tumic
  2022-09-20  2:12   ` kernel test robot
@ 2022-09-21  6:24   ` Lizhi Hou
  2022-09-21  9:25     ` Martin Tůma
  1 sibling, 1 reply; 13+ messages in thread
From: Lizhi Hou @ 2022-09-21  6:24 UTC (permalink / raw)
  To: tumic, Mauro Carvalho Chehab, Vinod Koul, Michal Simek
  Cc: linux-media, linux-kernel, dmaengine, linux-i2c, Martin Tůma

Hi Martin,

Glad to know that you are using XDMA IP in your product. And AMD/Xilinx 
is actually working on adding XDMA support to linux kernel.  The other 
kernel driver may use dmaengine APIs to transfer data through XDMA.

Please see the V3 patch series:

https://lore.kernel.org/lkml/1663631039-49732-1-git-send-email-lizhi.hou@amd.com/


Here is an example for using this V3 XDMA driver with Alveo accelerator 
PCIe device driver:

https://github.com/houlz0507/XRT-1/blob/qdma-v1/src/runtime_src/core/pcie/driver/linux/xocl/subdev/xdma.c#L338


Currently, the V3 patch series does not support register user logic 
interrupt yet. And you may refer this patch for it:

https://github.com/houlz0507/linux-xoclv2/commit/4f24f17862dbf527b7a32edf89559b7c732f1833


Could you convert your driver to use this?


Thanks,

Lizhi

On 9/19/22 11:55, tumic@gpxsee.org wrote:
> From: Martin Tůma <martin.tuma@digiteqautomotive.com>
>
> Added support for the Xilinx XDMA PCIe DMA IP core. The XDMA IP core is used in
> many FPGA PCIe card designs for DMA transfers between the PCIe card and the host
> system. This driver can be incorporated into any PCIe card (that contains
> the XDMA IP core) driver to initialize the XDMA HW and process DMA transfers.
>
> The driver is originally based on the code provided by Xilinx at
> https://github.com/Xilinx/dma_ip_drivers
>
> Signed-off-by: Martin Tůma <martin.tuma@digiteqautomotive.com>
> ---
>   drivers/dma/Kconfig              |    7 +
>   drivers/dma/xilinx/Makefile      |    1 +
>   drivers/dma/xilinx/xilinx_xdma.c | 2042 ++++++++++++++++++++++++++++++
>   include/linux/dma/xilinx_xdma.h  |   44 +
>   4 files changed, 2094 insertions(+)
>   create mode 100644 drivers/dma/xilinx/xilinx_xdma.c
>   create mode 100644 include/linux/dma/xilinx_xdma.h
>
> diff --git a/drivers/dma/Kconfig b/drivers/dma/Kconfig
> index a06d2a7627aa..932086cd5962 100644
> --- a/drivers/dma/Kconfig
> +++ b/drivers/dma/Kconfig
> @@ -804,4 +804,11 @@ config DMATEST
>   config DMA_ENGINE_RAID
>   	bool
>   
> +config XILINX_XDMA
> +	tristate "Xilinx XDMA Engine"
> +	depends on PCI
> +	select DMA_ENGINE
> +	help
> +	  Enable support for Xilinx XDMA IP controller.
> +
>   endif
> diff --git a/drivers/dma/xilinx/Makefile b/drivers/dma/xilinx/Makefile
> index 767bb45f641f..55e97686f8ea 100644
> --- a/drivers/dma/xilinx/Makefile
> +++ b/drivers/dma/xilinx/Makefile
> @@ -2,3 +2,4 @@
>   obj-$(CONFIG_XILINX_DMA) += xilinx_dma.o
>   obj-$(CONFIG_XILINX_ZYNQMP_DMA) += zynqmp_dma.o
>   obj-$(CONFIG_XILINX_ZYNQMP_DPDMA) += xilinx_dpdma.o
> +obj-$(CONFIG_XILINX_XDMA) += xilinx_xdma.o
> diff --git a/drivers/dma/xilinx/xilinx_xdma.c b/drivers/dma/xilinx/xilinx_xdma.c
> new file mode 100644
> index 000000000000..9db637c25045
> --- /dev/null
> +++ b/drivers/dma/xilinx/xilinx_xdma.c
> @@ -0,0 +1,2042 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * This file is part of the Xilinx DMA IP Core driver for Linux
> + *
> + * Copyright (c) 2016-2021,  Xilinx, Inc.
> + * Copyright (c) 2022,       Digiteq Automotive s.r.o.
> + */
> +
> +#include <linux/module.h>
> +#include <linux/kernel.h>
> +#include <linux/string.h>
> +#include <linux/mm.h>
> +#include <linux/errno.h>
> +#include <linux/sched.h>
> +#include <linux/vmalloc.h>
> +#include <linux/pci.h>
> +#include <linux/dma/xilinx_xdma.h>
> +
> +
> +static unsigned int enable_credit_mp = 1;
> +module_param(enable_credit_mp, uint, 0644);
> +MODULE_PARM_DESC(enable_credit_mp,
> +		 "Set 0 to disable credit feature, default is 1 (enabled)");
> +
> +#define XDMA_BAR_SIZE	0x8000UL
> +
> +#define XDMA_CHANNEL_NUM_MAX	4
> +#define XDMA_ENG_IRQ_NUM	1
> +#define XDMA_MAX_ADJ_BLOCK_SIZE	0x40
> +#define XDMA_PAGE_SIZE		0x1000
> +#define RX_STATUS_EOP 1
> +
> +#define XDMA_OFS_INT_CTRL	0x2000UL
> +#define XDMA_OFS_CONFIG		0x3000UL
> +
> +#define XDMA_TRANSFER_MAX_DESC	2048
> +
> +#define XDMA_DESC_BLEN_BITS	28
> +#define XDMA_DESC_BLEN_MAX	((1 << (XDMA_DESC_BLEN_BITS)) - 1)
> +
> +/* bits of the SG DMA control register */
> +#define XDMA_CTRL_RUN_STOP			(1UL << 0)
> +#define XDMA_CTRL_IE_DESC_STOPPED		(1UL << 1)
> +#define XDMA_CTRL_IE_DESC_COMPLETED		(1UL << 2)
> +#define XDMA_CTRL_IE_DESC_ALIGN_MISMATCH	(1UL << 3)
> +#define XDMA_CTRL_IE_MAGIC_STOPPED		(1UL << 4)
> +#define XDMA_CTRL_IE_IDLE_STOPPED		(1UL << 6)
> +#define XDMA_CTRL_IE_READ_ERROR			(0x1FUL << 9)
> +#define XDMA_CTRL_IE_DESC_ERROR			(0x1FUL << 19)
> +#define XDMA_CTRL_NON_INCR_ADDR			(1UL << 25)
> +#define XDMA_CTRL_POLL_MODE_WB			(1UL << 26)
> +#define XDMA_CTRL_STM_MODE_WB			(1UL << 27)
> +
> +/* bits of the SG DMA status register */
> +#define XDMA_STAT_BUSY			(1UL << 0)
> +#define XDMA_STAT_DESC_STOPPED		(1UL << 1)
> +#define XDMA_STAT_DESC_COMPLETED	(1UL << 2)
> +#define XDMA_STAT_ALIGN_MISMATCH	(1UL << 3)
> +#define XDMA_STAT_MAGIC_STOPPED		(1UL << 4)
> +#define XDMA_STAT_INVALID_LEN		(1UL << 5)
> +#define XDMA_STAT_IDLE_STOPPED		(1UL << 6)
> +
> +#define XDMA_STAT_COMMON_ERR_MASK \
> +	(XDMA_STAT_ALIGN_MISMATCH | XDMA_STAT_MAGIC_STOPPED | \
> +	 XDMA_STAT_INVALID_LEN)
> +
> +/* desc_error, C2H & H2C */
> +#define XDMA_STAT_DESC_UNSUPP_REQ	(1UL << 19)
> +#define XDMA_STAT_DESC_COMPL_ABORT	(1UL << 20)
> +#define XDMA_STAT_DESC_PARITY_ERR	(1UL << 21)
> +#define XDMA_STAT_DESC_HEADER_EP	(1UL << 22)
> +#define XDMA_STAT_DESC_UNEXP_COMPL	(1UL << 23)
> +
> +#define XDMA_STAT_DESC_ERR_MASK	\
> +	(XDMA_STAT_DESC_UNSUPP_REQ | XDMA_STAT_DESC_COMPL_ABORT | \
> +	 XDMA_STAT_DESC_PARITY_ERR | XDMA_STAT_DESC_HEADER_EP | \
> +	 XDMA_STAT_DESC_UNEXP_COMPL)
> +
> +/* read error: H2C */
> +#define XDMA_STAT_H2C_R_UNSUPP_REQ	(1UL << 9)
> +#define XDMA_STAT_H2C_R_COMPL_ABORT	(1UL << 10)
> +#define XDMA_STAT_H2C_R_PARITY_ERR	(1UL << 11)
> +#define XDMA_STAT_H2C_R_HEADER_EP	(1UL << 12)
> +#define XDMA_STAT_H2C_R_UNEXP_COMPL	(1UL << 13)
> +
> +#define XDMA_STAT_H2C_R_ERR_MASK	\
> +	(XDMA_STAT_H2C_R_UNSUPP_REQ | XDMA_STAT_H2C_R_COMPL_ABORT | \
> +	 XDMA_STAT_H2C_R_PARITY_ERR | XDMA_STAT_H2C_R_HEADER_EP | \
> +	 XDMA_STAT_H2C_R_UNEXP_COMPL)
> +
> +/* write error, H2C only */
> +#define XDMA_STAT_H2C_W_DECODE_ERR	(1UL << 14)
> +#define XDMA_STAT_H2C_W_SLAVE_ERR	(1UL << 15)
> +
> +#define XDMA_STAT_H2C_W_ERR_MASK	\
> +	(XDMA_STAT_H2C_W_DECODE_ERR | XDMA_STAT_H2C_W_SLAVE_ERR)
> +
> +/* read error: C2H */
> +#define XDMA_STAT_C2H_R_DECODE_ERR	(1UL << 9)
> +#define XDMA_STAT_C2H_R_SLAVE_ERR	(1UL << 10)
> +
> +#define XDMA_STAT_C2H_R_ERR_MASK	\
> +	(XDMA_STAT_C2H_R_DECODE_ERR | XDMA_STAT_C2H_R_SLAVE_ERR)
> +
> +/* all combined */
> +#define XDMA_STAT_H2C_ERR_MASK	\
> +	(XDMA_STAT_COMMON_ERR_MASK | XDMA_STAT_DESC_ERR_MASK | \
> +	 XDMA_STAT_H2C_R_ERR_MASK | XDMA_STAT_H2C_W_ERR_MASK)
> +
> +#define XDMA_STAT_C2H_ERR_MASK	\
> +	(XDMA_STAT_COMMON_ERR_MASK | XDMA_STAT_DESC_ERR_MASK | \
> +	 XDMA_STAT_C2H_R_ERR_MASK)
> +
> +/* bits of the SGDMA descriptor control field */
> +#define XDMA_DESC_STOPPED	(1UL << 0)
> +#define XDMA_DESC_COMPLETED	(1UL << 1)
> +#define XDMA_DESC_EOP		(1UL << 4)
> +
> +/* upper 16-bits of engine identifier register */
> +#define XDMA_ID_H2C 0x1fc0U
> +#define XDMA_ID_C2H 0x1fc1U
> +
> +#define LS_BYTE_MASK 0x000000FFUL
> +
> +#define BLOCK_ID_MASK 0xFFF00000
> +#define BLOCK_ID_HEAD 0x1FC00000
> +
> +#define IRQ_BLOCK_ID 0x1fc20000UL
> +#define CONFIG_BLOCK_ID 0x1fc30000UL
> +
> +#define WB_COUNT_MASK 0x00ffffffUL
> +#define WB_ERR_MASK (1UL << 31)
> +
> +#define MAX_USER_IRQ 16
> +
> +#define DESC_MAGIC 0xAD4B0000UL
> +
> +#define C2H_WB 0x52B4UL
> +
> +#define H2C_CHANNEL_OFFSET 0x1000
> +#define SGDMA_OFFSET_FROM_CHANNEL 0x4000
> +#define CHANNEL_SPACING 0x100
> +#define TARGET_SPACING 0x1000
> +
> +/* obtain the 32 most significant (high) bits of a 32-bit or 64-bit address */
> +#define PCI_DMA_H(addr) ((addr >> 16) >> 16)
> +/* obtain the 32 least significant (low) bits of a 32-bit or 64-bit address */
> +#define PCI_DMA_L(addr) (addr & 0xffffffffUL)
> +
> +
> +enum transfer_state {
> +	TRANSFER_STATE_NEW = 0,
> +	TRANSFER_STATE_SUBMITTED,
> +	TRANSFER_STATE_COMPLETED,
> +	TRANSFER_STATE_FAILED,
> +	TRANSFER_STATE_ABORTED
> +};
> +
> +enum shutdown_state {
> +	ENGINE_SHUTDOWN_NONE = 0,	/* No shutdown in progress */
> +	ENGINE_SHUTDOWN_REQUEST = 1,	/* engine requested to shutdown */
> +	ENGINE_SHUTDOWN_IDLE = 2	/* engine has shutdown and is idle */
> +};
> +
> +struct config_regs {
> +	u32 identifier;
> +	u32 reserved_1[4];
> +	u32 msi_enable;
> +};
> +
> +struct engine_regs {
> +	u32 identifier;
> +	u32 control;
> +	u32 control_w1s;
> +	u32 control_w1c;
> +	u32 reserved_1[12];	/* padding */
> +
> +	u32 status;
> +	u32 status_rc;
> +	u32 completed_desc_count;
> +	u32 alignments;
> +	u32 reserved_2[14];	/* padding */
> +
> +	u32 poll_mode_wb_lo;
> +	u32 poll_mode_wb_hi;
> +	u32 interrupt_enable_mask;
> +	u32 interrupt_enable_mask_w1s;
> +	u32 interrupt_enable_mask_w1c;
> +	u32 reserved_3[9];	/* padding */
> +
> +	u32 perf_ctrl;
> +	u32 perf_cyc_lo;
> +	u32 perf_cyc_hi;
> +	u32 perf_dat_lo;
> +	u32 perf_dat_hi;
> +	u32 perf_pnd_lo;
> +	u32 perf_pnd_hi;
> +} __packed;
> +
> +struct engine_sgdma_regs {
> +	u32 identifier;
> +	u32 reserved_1[31];	/* padding */
> +
> +	/* bus address to first descriptor in Root Complex Memory */
> +	u32 first_desc_lo;
> +	u32 first_desc_hi;
> +	/* number of adjacent descriptors at first_desc */
> +	u32 first_desc_adjacent;
> +	u32 credits;
> +} __packed;
> +
> +struct interrupt_regs {
> +	u32 identifier;
> +	u32 user_int_enable;
> +	u32 user_int_enable_w1s;
> +	u32 user_int_enable_w1c;
> +	u32 channel_int_enable;
> +	u32 channel_int_enable_w1s;
> +	u32 channel_int_enable_w1c;
> +	u32 reserved_1[9];	/* padding */
> +
> +	u32 user_int_request;
> +	u32 channel_int_request;
> +	u32 user_int_pending;
> +	u32 channel_int_pending;
> +	u32 reserved_2[12];	/* padding */
> +
> +	u32 user_msi_vector[8];
> +	u32 channel_msi_vector[8];
> +} __packed;
> +
> +struct sgdma_common_regs {
> +	u32 padding[8];
> +	u32 credit_mode_enable;
> +	u32 credit_mode_enable_w1s;
> +	u32 credit_mode_enable_w1c;
> +} __packed;
> +
> +
> +/*
> + * Descriptor for a single contiguous memory block transfer.
> + *
> + * Multiple descriptors are linked by means of the next pointer. An additional
> + * extra adjacent number gives the amount of extra contiguous descriptors.
> + *
> + * The descriptors are in root complex memory, and the bytes in the 32-bit
> + * words must be in little-endian byte ordering.
> + */
> +struct xdma_desc {
> +	u32 control;
> +	u32 bytes;		/* transfer length in bytes */
> +	u32 src_addr_lo;	/* source address (low 32-bit) */
> +	u32 src_addr_hi;	/* source address (high 32-bit) */
> +	u32 dst_addr_lo;	/* destination address (low 32-bit) */
> +	u32 dst_addr_hi;	/* destination address (high 32-bit) */
> +	/*
> +	 * next descriptor in the single-linked list of descriptors;
> +	 * this is the PCIe (bus) address of the next descriptor in the
> +	 * root complex memory
> +	 */
> +	u32 next_lo;		/* next desc address (low 32-bit) */
> +	u32 next_hi;		/* next desc address (high 32-bit) */
> +} __packed;
> +
> +/* 32 bytes (four 32-bit words) or 64 bytes (eight 32-bit words) */
> +struct xdma_result {
> +	u32 status;
> +	u32 length;
> +	u32 reserved_1[6];	/* padding */
> +} __packed;
> +
> +struct sw_desc {
> +	dma_addr_t addr;
> +	unsigned int len;
> +};
> +
> +/* Describes a (SG DMA) single transfer for the engine */
> +#define XFER_FLAG_NEED_UNMAP		0x1
> +#define XFER_FLAG_ST_C2H_EOP_RCVED	0x2	/* ST c2h only */
> +struct xdma_transfer {
> +	struct list_head entry;		/* queue of non-completed transfers */
> +	struct xdma_desc *desc_virt;	/* virt addr of the 1st descriptor */
> +	struct xdma_result *res_virt;   /* virt addr of result, c2h streaming */
> +	dma_addr_t res_bus;		/* bus addr for result descriptors */
> +	dma_addr_t desc_bus;		/* bus addr of the first descriptor */
> +	int desc_adjacent;		/* adjacent descriptors at desc_bus */
> +	int desc_num;			/* number of descriptors in transfer */
> +	int desc_index;			/* index for 1st desc. in transfer */
> +	int desc_cmpl;			/* completed descriptors */
> +	int desc_cmpl_th;		/* completed descriptor threshold */
> +	enum dma_data_direction dir;
> +	struct swait_queue_head wq;	/* wait queue for transfer completion */
> +
> +	enum transfer_state state;	/* state of the transfer */
> +	unsigned int flags;
> +	int cyclic;			/* flag if transfer is cyclic */
> +	int last_in_request;		/* flag if last within request */
> +	unsigned int len;
> +	struct sg_table *sgt;
> +};
> +
> +struct xdma_request_cb {
> +	struct sg_table *sgt;
> +	unsigned int total_len;
> +	u64 ep_addr;
> +
> +	struct xdma_transfer tfer;
> +
> +	unsigned int sw_desc_idx;
> +	unsigned int sw_desc_cnt;
> +	struct sw_desc sdesc[0];
> +};
> +
> +struct xdma_engine {
> +	struct xdma_dev *xdev;	/* parent device */
> +	char name[16];		/* name of this engine */
> +
> +	/* HW register address offsets */
> +	struct engine_regs *regs;		/* Control reg BAR offset */
> +	struct engine_sgdma_regs *sgdma_regs;	/* SGDAM reg BAR offset */
> +
> +	/* Engine state, configuration and flags */
> +	enum shutdown_state shutdown;	/* engine shutdown mode */
> +	enum dma_data_direction dir;
> +	u8 addr_align;		/* source/dest alignment in bytes */
> +	u8 len_granularity;	/* transfer length multiple */
> +	u8 addr_bits;		/* HW datapath address width */
> +	u8 channel:2;		/* engine indices */
> +	u8 streaming:1;
> +	u8 device_open:1;	/* flag if engine node open, ST mode only */
> +	u8 running:1;		/* flag if the driver started engine */
> +	u8 non_incr_addr:1;	/* flag if non-incremental addressing used */
> +	u8 eop_flush:1;		/* st c2h only, flush up the data with eop */
> +	u8 filler:1;
> +
> +	int max_extra_adj;	/* descriptor prefetch capability */
> +	int desc_dequeued;	/* num descriptors of completed transfers */
> +	u32 status;		/* last known status of device */
> +	u32 interrupt_enable_mask_value; /* per-engine interrupt mask value */
> +
> +	/* Transfer list management */
> +	struct list_head transfer_list;	/* queue of transfers */
> +
> +	/* Members applicable to AXI-ST C2H (cyclic) transfers */
> +	struct xdma_result *cyclic_result;
> +	dma_addr_t cyclic_result_bus;	/* bus addr for transfer */
> +
> +	/* Members associated with interrupt mode support */
> +	struct swait_queue_head shutdown_wq;
> +	spinlock_t lock;		/* protects concurrent access */
> +	int prev_cpu;			/* remember CPU# of (last) locker */
> +	int irq_line;			/* IRQ vector for this engine */
> +	u32 irq_bitmask;		/* IRQ bit mask for this engine */
> +	struct work_struct work;	/* Work queue for interrupt handling */
> +
> +	struct mutex desc_lock;		/* protects concurrent access */
> +	dma_addr_t desc_bus;
> +	struct xdma_desc *desc;
> +	int desc_idx;			/* current descriptor index */
> +	int desc_used;			/* total descriptors used */
> +};
> +
> +struct xdma_dev {
> +	struct pci_dev *pdev;
> +	void __iomem *config_bar;
> +	unsigned int mask_irq_user;
> +	int engines_num;
> +	struct xdma_engine engine_h2c[XDMA_CHANNEL_NUM_MAX];
> +	struct xdma_engine engine_c2h[XDMA_CHANNEL_NUM_MAX];
> +};
> +
> +
> +static void channel_interrupts_enable(struct xdma_dev *xdev, u32 mask)
> +{
> +	struct interrupt_regs *reg =
> +		(struct interrupt_regs *)(xdev->config_bar + XDMA_OFS_INT_CTRL);
> +
> +	iowrite32(mask, &reg->channel_int_enable_w1s);
> +}
> +
> +static void channel_interrupts_disable(struct xdma_dev *xdev, u32 mask)
> +{
> +	struct interrupt_regs *reg =
> +		(struct interrupt_regs *)(xdev->config_bar + XDMA_OFS_INT_CTRL);
> +
> +	iowrite32(mask, &reg->channel_int_enable_w1c);
> +}
> +
> +static void user_interrupts_enable(struct xdma_dev *xdev, u32 mask)
> +{
> +	struct interrupt_regs *reg =
> +		(struct interrupt_regs *)(xdev->config_bar + XDMA_OFS_INT_CTRL);
> +
> +	iowrite32(mask, &reg->user_int_enable_w1s);
> +}
> +
> +static void user_interrupts_disable(struct xdma_dev *xdev, u32 mask)
> +{
> +	struct interrupt_regs *reg =
> +		(struct interrupt_regs *)(xdev->config_bar + XDMA_OFS_INT_CTRL);
> +
> +	iowrite32(mask, &reg->user_int_enable_w1c);
> +}
> +
> +static void read_interrupts(struct xdma_dev *xdev)
> +{
> +	struct interrupt_regs *reg =
> +		(struct interrupt_regs *)(xdev->config_bar + XDMA_OFS_INT_CTRL);
> +	u32 lo, hi;
> +
> +	hi = ioread32(&reg->user_int_request);
> +	lo = ioread32(&reg->channel_int_request);
> +}
> +
> +static void engine_reg_dump(struct xdma_engine *engine)
> +{
> +	u32 w;
> +
> +	w = ioread32(&engine->regs->identifier);
> +	if ((w & BLOCK_ID_MASK) != BLOCK_ID_HEAD) {
> +		pr_warn("XDMA: %s: 0x%08x: invalid engine id\n",
> +		       engine->name, w);
> +		return;
> +	}
> +
> +	pr_info("XDMA: %s: ENGINE REGISTER DUMP\n", engine->name);
> +	pr_info("%s: ioread32(0x%p) = 0x%08x (id).\n",
> +		engine->name, &engine->regs->identifier, w);
> +	w = ioread32(&engine->regs->status);
> +	pr_info("%s: ioread32(0x%p) = 0x%08x (status).\n",
> +		engine->name, &engine->regs->status, w);
> +	w = ioread32(&engine->regs->control);
> +	pr_info("%s: ioread32(0x%p) = 0x%08x (control)\n",
> +		engine->name, &engine->regs->control, w);
> +	w = ioread32(&engine->sgdma_regs->first_desc_lo);
> +	pr_info("%s: ioread32(0x%p) = 0x%08x (first_desc_lo)\n",
> +		engine->name, &engine->sgdma_regs->first_desc_lo, w);
> +	w = ioread32(&engine->sgdma_regs->first_desc_hi);
> +	pr_info("%s: ioread32(0x%p) = 0x%08x (first_desc_hi)\n",
> +		engine->name, &engine->sgdma_regs->first_desc_hi, w);
> +	w = ioread32(&engine->sgdma_regs->first_desc_adjacent);
> +	pr_info("%s: ioread32(0x%p) = 0x%08x (first_desc_adjacent).\n",
> +		engine->name, &engine->sgdma_regs->first_desc_adjacent, w);
> +	w = ioread32(&engine->regs->completed_desc_count);
> +	pr_info("%s: ioread32(0x%p) = 0x%08x (completed_desc_count).\n",
> +		engine->name, &engine->regs->completed_desc_count, w);
> +	w = ioread32(&engine->regs->interrupt_enable_mask);
> +	pr_info("%s: ioread32(0x%p) = 0x%08x (interrupt_enable_mask)\n",
> +		engine->name, &engine->regs->interrupt_enable_mask, w);
> +}
> +
> +static void engine_status_dump(struct xdma_engine *engine)
> +{
> +	u32 v = engine->status;
> +	char buffer[256];
> +	char *buf = buffer;
> +	int len = 0;
> +
> +	len = sprintf(buf, "XDMA: %s: status: 0x%08x: ", engine->name, v);
> +
> +	if ((v & XDMA_STAT_BUSY))
> +		len += sprintf(buf + len, "BUSY,");
> +	if ((v & XDMA_STAT_DESC_STOPPED))
> +		len += sprintf(buf + len, "DESC_STOPPED,");
> +	if ((v & XDMA_STAT_DESC_COMPLETED))
> +		len += sprintf(buf + len, "DESC_COMPL,");
> +
> +	/* common H2C & C2H */
> +	if ((v & XDMA_STAT_COMMON_ERR_MASK)) {
> +		if ((v & XDMA_STAT_ALIGN_MISMATCH))
> +			len += sprintf(buf + len, "ALIGN_MISMATCH ");
> +		if ((v & XDMA_STAT_MAGIC_STOPPED))
> +			len += sprintf(buf + len, "MAGIC_STOPPED ");
> +		if ((v & XDMA_STAT_INVALID_LEN))
> +			len += sprintf(buf + len, "INVLIAD_LEN ");
> +		if ((v & XDMA_STAT_IDLE_STOPPED))
> +			len += sprintf(buf + len, "IDLE_STOPPED ");
> +		buf[len - 1] = ',';
> +	}
> +
> +	if (engine->dir == DMA_TO_DEVICE) {
> +		/* H2C only */
> +		if ((v & XDMA_STAT_H2C_R_ERR_MASK)) {
> +			len += sprintf(buf + len, "R:");
> +			if ((v & XDMA_STAT_H2C_R_UNSUPP_REQ))
> +				len += sprintf(buf + len, "UNSUPP_REQ ");
> +			if ((v & XDMA_STAT_H2C_R_COMPL_ABORT))
> +				len += sprintf(buf + len, "COMPL_ABORT ");
> +			if ((v & XDMA_STAT_H2C_R_PARITY_ERR))
> +				len += sprintf(buf + len, "PARITY ");
> +			if ((v & XDMA_STAT_H2C_R_HEADER_EP))
> +				len += sprintf(buf + len, "HEADER_EP ");
> +			if ((v & XDMA_STAT_H2C_R_UNEXP_COMPL))
> +				len += sprintf(buf + len, "UNEXP_COMPL ");
> +			buf[len - 1] = ',';
> +		}
> +
> +		if ((v & XDMA_STAT_H2C_W_ERR_MASK)) {
> +			len += sprintf(buf + len, "W:");
> +			if ((v & XDMA_STAT_H2C_W_DECODE_ERR))
> +				len += sprintf(buf + len, "DECODE_ERR ");
> +			if ((v & XDMA_STAT_H2C_W_SLAVE_ERR))
> +				len += sprintf(buf + len, "SLAVE_ERR ");
> +			buf[len - 1] = ',';
> +		}
> +
> +	} else {
> +		/* C2H only */
> +		if ((v & XDMA_STAT_C2H_R_ERR_MASK)) {
> +			len += sprintf(buf + len, "R:");
> +			if ((v & XDMA_STAT_C2H_R_DECODE_ERR))
> +				len += sprintf(buf + len, "DECODE_ERR ");
> +			if ((v & XDMA_STAT_C2H_R_SLAVE_ERR))
> +				len += sprintf(buf + len, "SLAVE_ERR ");
> +			buf[len - 1] = ',';
> +		}
> +	}
> +
> +	/* common H2C & C2H */
> +	if ((v & XDMA_STAT_DESC_ERR_MASK)) {
> +		len += sprintf(buf + len, "DESC_ERR:");
> +		if ((v & XDMA_STAT_DESC_UNSUPP_REQ))
> +			len += sprintf(buf + len, "UNSUPP_REQ ");
> +		if ((v & XDMA_STAT_DESC_COMPL_ABORT))
> +			len += sprintf(buf + len, "COMPL_ABORT ");
> +		if ((v & XDMA_STAT_DESC_PARITY_ERR))
> +			len += sprintf(buf + len, "PARITY ");
> +		if ((v & XDMA_STAT_DESC_HEADER_EP))
> +			len += sprintf(buf + len, "HEADER_EP ");
> +		if ((v & XDMA_STAT_DESC_UNEXP_COMPL))
> +			len += sprintf(buf + len, "UNEXP_COMPL ");
> +		buf[len - 1] = ',';
> +	}
> +
> +	buf[len - 1] = '\0';
> +	pr_info("%s\n", buffer);
> +}
> +
> +static void engine_status_read(struct xdma_engine *engine, bool clear, bool dump)
> +{
> +	if (dump)
> +		engine_reg_dump(engine);
> +
> +	if (clear)
> +		engine->status = ioread32(&engine->regs->status_rc);
> +	else
> +		engine->status = ioread32(&engine->regs->status);
> +
> +	if (dump)
> +		engine_status_dump(engine);
> +}
> +
> +static void engine_stop(struct xdma_engine *engine)
> +{
> +	u32 w;
> +
> +	if (enable_credit_mp && engine->streaming &&
> +	    engine->dir == DMA_FROM_DEVICE)
> +		iowrite32(0, &engine->sgdma_regs->credits);
> +
> +	w = 0;
> +	w |= (u32)XDMA_CTRL_IE_DESC_ALIGN_MISMATCH;
> +	w |= (u32)XDMA_CTRL_IE_MAGIC_STOPPED;
> +	w |= (u32)XDMA_CTRL_IE_READ_ERROR;
> +	w |= (u32)XDMA_CTRL_IE_DESC_ERROR;
> +
> +	w |= (u32)XDMA_CTRL_IE_DESC_STOPPED;
> +	w |= (u32)XDMA_CTRL_IE_DESC_COMPLETED;
> +
> +	iowrite32(w, &engine->regs->control);
> +
> +	engine->running = 0;
> +}
> +
> +static int engine_start_mode_config(struct xdma_engine *engine)
> +{
> +	u32 w;
> +
> +	/* write control register of SG DMA engine */
> +	w = (u32)XDMA_CTRL_RUN_STOP;
> +	w |= (u32)XDMA_CTRL_IE_READ_ERROR;
> +	w |= (u32)XDMA_CTRL_IE_DESC_ERROR;
> +	w |= (u32)XDMA_CTRL_IE_DESC_ALIGN_MISMATCH;
> +	w |= (u32)XDMA_CTRL_IE_MAGIC_STOPPED;
> +
> +	w |= (u32)XDMA_CTRL_IE_DESC_STOPPED;
> +	w |= (u32)XDMA_CTRL_IE_DESC_COMPLETED;
> +
> +	/* set non-incremental addressing mode */
> +	if (engine->non_incr_addr)
> +		w |= (u32)XDMA_CTRL_NON_INCR_ADDR;
> +
> +	/* start the engine */
> +	iowrite32(w, &engine->regs->control);
> +	/* dummy read of status register to flush all previous writes */
> +	w = ioread32(&engine->regs->status);
> +
> +	return 0;
> +}
> +
> +/*
> + * Get the number for adjacent descriptors to set in a descriptor, based on the
> + * remaining number of descriptors and the lower bits of the address of the
> + * next descriptor.
> + * Since the number of descriptors in a page (XDMA_PAGE_SIZE) is 128 and the
> + * maximum size of a block of adjacent descriptors is 64 (63 max adjacent
> + * descriptors for any descriptor), align the blocks of adjacent descriptors
> + * to the block size.
> + */
> +static u32 xdma_get_next_adj(unsigned int remaining, u32 next_lo)
> +{
> +	unsigned int next_index;
> +
> +	if (remaining <= 1)
> +		return 0;
> +
> +	/* shift right 5 times corresponds to a division by
> +	 * sizeof(xdma_desc) = 32
> +	 */
> +	next_index = ((next_lo & (XDMA_PAGE_SIZE - 1)) >> 5) %
> +		XDMA_MAX_ADJ_BLOCK_SIZE;
> +	return min(XDMA_MAX_ADJ_BLOCK_SIZE - next_index - 1, remaining - 1);
> +}
> +
> +/*
> + * start an idle engine with its first transfer on queue
> + *
> + * The engine will run and process all transfers that are queued using
> + * transfer_queue() and thus have their descriptor lists chained.
> + *
> + * During the run, new transfers will be processed if transfer_queue() has
> + * chained the descriptors before the hardware fetches the last descriptor.
> + * A transfer that was chained too late will invoke a new run of the engine
> + * initiated from the engine_service() routine.
> + *
> + * The engine must be idle and at least one transfer must be queued.
> + */
> +static int engine_start(struct xdma_engine *engine)
> +{
> +	struct xdma_transfer *transfer;
> +	u32 w, next_adj;
> +	int rv;
> +
> +	/* engine transfer queue must not be empty */
> +	if (list_empty(&engine->transfer_list)) {
> +		pr_warn("XDMA: %s: transfer queue must not be empty\n",
> +			engine->name);
> +		return -EIO;
> +	}
> +	/* inspect first transfer queued on the engine */
> +	transfer = list_entry(engine->transfer_list.next, struct xdma_transfer,
> +			      entry);
> +	if (!transfer) {
> +		pr_warn("XDMA: %s: queued transfer must not be empty\n",
> +			engine->name);
> +		return -EIO;
> +	}
> +
> +	/* engine is no longer shutdown */
> +	engine->shutdown = ENGINE_SHUTDOWN_NONE;
> +
> +	/* Add credits for Streaming mode C2H */
> +	if (enable_credit_mp && engine->streaming &&
> +	    engine->dir == DMA_FROM_DEVICE)
> +		iowrite32(engine->desc_used, &engine->sgdma_regs->credits);
> +
> +	/* initialize number of descriptors of dequeued transfers */
> +	engine->desc_dequeued = 0;
> +
> +	/* write lower 32-bit of bus address of transfer first descriptor */
> +	w = cpu_to_le32(PCI_DMA_L(transfer->desc_bus));
> +	iowrite32(w, &engine->sgdma_regs->first_desc_lo);
> +	/* write upper 32-bit of bus address of transfer first descriptor */
> +	w = cpu_to_le32(PCI_DMA_H(transfer->desc_bus));
> +	iowrite32(w, &engine->sgdma_regs->first_desc_hi);
> +
> +	next_adj = xdma_get_next_adj(transfer->desc_adjacent,
> +				     cpu_to_le32(PCI_DMA_L(transfer->desc_bus)));
> +	iowrite32(next_adj, &engine->sgdma_regs->first_desc_adjacent);
> +
> +	rv = engine_start_mode_config(engine);
> +	if (rv < 0)
> +		return rv;
> +	engine_status_read(engine, 0, 0);
> +
> +	engine->running = 1;
> +
> +	return 0;
> +}
> +
> +static void engine_service_shutdown(struct xdma_engine *engine)
> +{
> +	engine_stop(engine);
> +	/* awake task on engine's shutdown wait queue */
> +	swake_up_one(&engine->shutdown_wq);
> +}
> +
> +static struct xdma_transfer *engine_transfer_completion(
> +		struct xdma_engine *engine,
> +		struct xdma_transfer *transfer)
> +{
> +	if (unlikely(!transfer)) {
> +		pr_warn("XDMA: %s empty xfer\n", engine->name);
> +		return NULL;
> +	}
> +
> +	/* synchronous I/O? */
> +	/* awake task on transfer's wait queue */
> +	swake_up_one(&transfer->wq);
> +
> +	return transfer;
> +}
> +
> +static struct xdma_transfer *engine_service_transfer_list(
> +		struct xdma_engine *engine,
> +		struct xdma_transfer *transfer,
> +		u32 *pdesc_completed)
> +{
> +	if (unlikely(!transfer)) {
> +		pr_warn("XDMA: %s empty xfer\n", engine->name);
> +		return NULL;
> +	}
> +
> +	/*
> +	 * iterate over all the transfers completed by the engine,
> +	 * except for the last
> +	 */
> +	while (transfer && (!transfer->cyclic) &&
> +	       (*pdesc_completed > transfer->desc_num)) {
> +		/* remove this transfer from pdesc_completed */
> +		*pdesc_completed -= transfer->desc_num;
> +
> +		/* remove completed transfer from list */
> +		list_del(engine->transfer_list.next);
> +		/* add to dequeued number of descriptors during this run */
> +		engine->desc_dequeued += transfer->desc_num;
> +		/* mark transfer as successfully completed */
> +		transfer->state = TRANSFER_STATE_COMPLETED;
> +
> +		/*
> +		 * Complete transfer - sets transfer to NULL if an async
> +		 * transfer has completed
> +		 */
> +		transfer = engine_transfer_completion(engine, transfer);
> +
> +		/* if exists, get the next transfer on the list */
> +		if (!list_empty(&engine->transfer_list)) {
> +			transfer = list_entry(engine->transfer_list.next,
> +					      struct xdma_transfer, entry);
> +		} else {
> +			/* no further transfers? */
> +			transfer = NULL;
> +		}
> +	}
> +
> +	return transfer;
> +}
> +
> +static void engine_err_handle(struct xdma_engine *engine,
> +			      struct xdma_transfer *transfer)
> +{
> +	u32 value;
> +
> +	/*
> +	 * The BUSY bit is expected to be clear now but older HW has a race
> +	 * condition which could cause it to be still set.  If it's set, re-read
> +	 * and check again.  If it's still set, log the issue.
> +	 */
> +	if (engine->status & XDMA_STAT_BUSY) {
> +		value = ioread32(&engine->regs->status);
> +		if ((value & XDMA_STAT_BUSY))
> +			pr_warn("XDMA: %s has errors but is still BUSY\n",
> +				engine->name);
> +	}
> +
> +	/* mark transfer as failed */
> +	transfer->state = TRANSFER_STATE_FAILED;
> +	engine_stop(engine);
> +}
> +
> +static struct xdma_transfer *
> +engine_service_final_transfer(struct xdma_engine *engine,
> +			      struct xdma_transfer *transfer,
> +			      u32 *pdesc_completed)
> +{
> +	/* inspect the current transfer */
> +	if (unlikely(!transfer)) {
> +		pr_warn("XDMA: %s: empty xfer\n", engine->name);
> +		return NULL;
> +	}
> +
> +	if (((engine->dir == DMA_FROM_DEVICE) &&
> +	     (engine->status & XDMA_STAT_C2H_ERR_MASK)) ||
> +	    ((engine->dir == DMA_TO_DEVICE) &&
> +	     (engine->status & XDMA_STAT_H2C_ERR_MASK))) {
> +		pr_warn("XDMA: %s: status error 0x%x.\n", engine->name,
> +			engine->status);
> +		engine_status_dump(engine);
> +		engine_err_handle(engine, transfer);
> +		goto transfer_del;
> +	}
> +
> +	if (engine->status & XDMA_STAT_BUSY)
> +		pr_info("XDMA: %s: engine unexpectedly busy, ignoring\n",
> +			engine->name);
> +
> +	/* the engine stopped on current transfer? */
> +	if (*pdesc_completed < transfer->desc_num) {
> +		if (engine->eop_flush) {
> +			/* check if eop received */
> +			struct xdma_result *result = transfer->res_virt;
> +			int i;
> +			int max = *pdesc_completed;
> +
> +			for (i = 0; i < max; i++) {
> +				if ((result[i].status & RX_STATUS_EOP) != 0) {
> +					transfer->flags |=
> +						XFER_FLAG_ST_C2H_EOP_RCVED;
> +					break;
> +				}
> +			}
> +
> +			transfer->desc_cmpl += *pdesc_completed;
> +			if (!(transfer->flags & XFER_FLAG_ST_C2H_EOP_RCVED))
> +				return NULL;
> +
> +			/* mark transfer as successfully completed */
> +			engine_service_shutdown(engine);
> +			transfer->state = TRANSFER_STATE_COMPLETED;
> +			engine->desc_dequeued += transfer->desc_cmpl;
> +		} else {
> +			transfer->state = TRANSFER_STATE_FAILED;
> +			pr_warn("XDMA: %s: xfer stopped half-way\n",
> +				engine->name);
> +
> +			/* add dequeued number of descriptors during this run */
> +			engine->desc_dequeued += transfer->desc_num;
> +			transfer->desc_cmpl = *pdesc_completed;
> +		}
> +	} else {
> +		if (!transfer->cyclic) {
> +			/*
> +			 * if the engine stopped on this transfer,
> +			 * it should be the last
> +			 */
> +			WARN_ON(*pdesc_completed > transfer->desc_num);
> +		}
> +		/* mark transfer as successfully completed */
> +		transfer->state = TRANSFER_STATE_COMPLETED;
> +		transfer->desc_cmpl = transfer->desc_num;
> +		/* add dequeued number of descriptors during this run */
> +		engine->desc_dequeued += transfer->desc_num;
> +	}
> +
> +transfer_del:
> +	/* remove completed transfer from list */
> +	list_del(engine->transfer_list.next);
> +
> +	/*
> +	 * Complete transfer - sets transfer to NULL if an asynchronous
> +	 * transfer has completed
> +	 */
> +	transfer = engine_transfer_completion(engine, transfer);
> +
> +	return transfer;
> +}
> +
> +static int engine_service_resume(struct xdma_engine *engine)
> +{
> +	int rv;
> +
> +	if (!engine->running) {
> +		/* in the case of shutdown, let it finish what's in the Q */
> +		if (!list_empty(&engine->transfer_list)) {
> +			/* (re)start engine */
> +			rv = engine_start(engine);
> +			if (rv)
> +				return rv;
> +			/* engine was requested to be shutdown? */
> +		} else if (engine->shutdown & ENGINE_SHUTDOWN_REQUEST) {
> +			engine->shutdown |= ENGINE_SHUTDOWN_IDLE;
> +			/* awake task on engine's shutdown wait queue */
> +			swake_up_one(&engine->shutdown_wq);
> +		}
> +	} else if (list_empty(&engine->transfer_list)) {
> +		engine_service_shutdown(engine);
> +	}
> +
> +	return 0;
> +}
> +
> +static int engine_service(struct xdma_engine *engine, int desc_writeback)
> +{
> +	struct xdma_transfer *transfer = NULL;
> +	u32 desc_count = desc_writeback & WB_COUNT_MASK;
> +	u32 err_flag = desc_writeback & WB_ERR_MASK;
> +	int rv;
> +
> +	if (!engine->running) {
> +		engine_status_read(engine, 1, 0);
> +		return 0;
> +	}
> +
> +	/*
> +	 * If called by the ISR detected an error, read and clear
> +	 * engine status.
> +	 */
> +	if ((desc_count == 0) || (err_flag != 0))
> +		engine_status_read(engine, 1, 0);
> +
> +	/*
> +	 * engine was running but is no longer busy, or writeback occurred,
> +	 * shut down
> +	 */
> +	if ((engine->running && !(engine->status & XDMA_STAT_BUSY)) ||
> +	    (!engine->eop_flush && desc_count != 0))
> +		engine_service_shutdown(engine);
> +
> +	/*
> +	 * If called from the ISR, or if an error occurred, the descriptor
> +	 * count will be zero.  In this scenario, read the descriptor count
> +	 * from HW.
> +	 */
> +	if (!desc_count)
> +		desc_count = ioread32(&engine->regs->completed_desc_count);
> +	if (!desc_count)
> +		goto done;
> +
> +	/* transfers on queue? */
> +	if (!list_empty(&engine->transfer_list)) {
> +		/* pick first transfer on queue (was submitted to the engine) */
> +		transfer = list_entry(engine->transfer_list.next,
> +				      struct xdma_transfer, entry);
> +	}
> +
> +	/* account for already dequeued transfers during this engine run */
> +	desc_count -= engine->desc_dequeued;
> +
> +	/* Process all but the last transfer */
> +	transfer = engine_service_transfer_list(engine, transfer, &desc_count);
> +
> +	/*
> +	 * Process final transfer - includes checks of number of descriptors to
> +	 * detect faulty completion
> +	 */
> +	transfer = engine_service_final_transfer(engine, transfer, &desc_count);
> +
> +	/* Restart the engine following the servicing */
> +	if (!engine->eop_flush) {
> +		rv = engine_service_resume(engine);
> +		if (rv)
> +			return rv;
> +	}
> +
> +done:
> +	return err_flag ? -1 : 0;
> +}
> +
> +static void engine_service_work(struct work_struct *work)
> +{
> +	struct xdma_engine *engine;
> +	unsigned long flags;
> +	int rv;
> +
> +	engine = container_of(work, struct xdma_engine, work);
> +
> +	spin_lock_irqsave(&engine->lock, flags);
> +
> +	rv = engine_service(engine, 0);
> +	if (rv < 0)
> +		goto unlock;
> +
> +	/* re-enable interrupts for this engine */
> +	iowrite32(engine->interrupt_enable_mask_value,
> +		  &engine->regs->interrupt_enable_mask_w1s);
> +
> +unlock:
> +	spin_unlock_irqrestore(&engine->lock, flags);
> +}
> +
> +static irqreturn_t xdma_isr(int irq, void *dev_id)
> +{
> +	struct xdma_dev *xdev;
> +	struct xdma_engine *engine;
> +	struct interrupt_regs *irq_regs;
> +
> +	engine = (struct xdma_engine *)dev_id;
> +	xdev = engine->xdev;
> +
> +	irq_regs = (struct interrupt_regs *)(xdev->config_bar + XDMA_OFS_INT_CTRL);
> +
> +	/* Disable the interrupt for this engine */
> +	iowrite32(engine->interrupt_enable_mask_value,
> +		&engine->regs->interrupt_enable_mask_w1c);
> +	/* Dummy read to flush the above write */
> +	ioread32(&irq_regs->channel_int_pending);
> +	schedule_work(&engine->work);
> +
> +	return IRQ_HANDLED;
> +}
> +
> +static int is_config_bar(void *bar)
> +{
> +	u32 irq_id = 0;
> +	u32 cfg_id = 0;
> +	u32 mask = 0xffff0000; /* Compare only XDMA ID's not Version number */
> +	struct interrupt_regs *irq_regs =
> +		(struct interrupt_regs *)(bar + XDMA_OFS_INT_CTRL);
> +	struct config_regs *cfg_regs =
> +		(struct config_regs *)(bar + XDMA_OFS_CONFIG);
> +
> +	irq_id = ioread32(&irq_regs->identifier);
> +	cfg_id = ioread32(&cfg_regs->identifier);
> +
> +	if (((irq_id & mask) == IRQ_BLOCK_ID)
> +	    && ((cfg_id & mask) == CONFIG_BLOCK_ID))
> +		return 1;
> +
> +	return 0;
> +}
> +
> +static void unmap_config_bar(struct xdma_dev *xdev, int config_bar_id)
> +{
> +	pci_iounmap(xdev->pdev, xdev->config_bar);
> +	pci_release_selected_regions(xdev->pdev, 1U<<config_bar_id);
> +}
> +
> +static int map_config_bar(struct xdma_dev *xdev, int config_bar_id)
> +{
> +	int rv, bar_len;
> +
> +	bar_len = pci_resource_len(xdev->pdev, config_bar_id);
> +	if (bar_len < XDMA_BAR_SIZE) {
> +		pr_err("XDMA: %d: Not a config BAR\n", config_bar_id);
> +		return -EINVAL;
> +	}
> +	rv = pci_request_selected_regions(xdev->pdev, 1U<<config_bar_id,
> +					  "xdma");
> +	if (rv) {
> +		pr_err("XDMA: Failed to request config BAR memory\n");
> +		return rv;
> +	}
> +	xdev->config_bar = pci_iomap(xdev->pdev, config_bar_id, bar_len);
> +	if (!xdev->config_bar) {
> +		pr_err("XDMA: Failed to map config BAR memory\n");
> +		rv = -ENOMEM;
> +		goto err_map;
> +	}
> +	if (!is_config_bar(xdev->config_bar)) {
> +		pr_err("XDMA: %d: Not a config BAR\n", config_bar_id);
> +		rv = -EINVAL;
> +		goto err_bar;
> +	}
> +
> +	pr_debug("XDMA: Config BAR %d mapped at %p\n",
> +		 config_bar_id, xdev->config_bar);
> +
> +	return 0;
> +
> +err_bar:
> +	pci_iounmap(xdev->pdev, xdev->config_bar);
> +err_map:
> +	pci_release_selected_regions(xdev->pdev, 1U<<config_bar_id);
> +
> +	return rv;
> +}
> +
> +static void prog_irq_user(struct xdma_dev *xdev, int num_channel, int num_irq,
> +			  bool clear)
> +{
> +	struct interrupt_regs *int_regs =
> +		(struct interrupt_regs *)(xdev->config_bar + XDMA_OFS_INT_CTRL);
> +	int i = num_channel;
> +	int max = i + num_irq;
> +	int j;
> +
> +	for (j = 0; i < max; j++) {
> +		u32 val = 0;
> +		int k, shift = 0;
> +
> +		if (clear)
> +			i += 4;
> +		else
> +			for (k = 0; k < 4 && i < max; i++, k++, shift += 8)
> +				val |= (i & 0x1f) << shift;
> +
> +		iowrite32(val, &int_regs->user_msi_vector[j]);
> +	}
> +}
> +
> +static void prog_irq_channel(struct xdma_dev *xdev, int num_channel, bool clear)
> +{
> +	struct interrupt_regs *int_regs =
> +		(struct interrupt_regs *)(xdev->config_bar + XDMA_OFS_INT_CTRL);
> +	int i, j;
> +
> +	for (i = 0, j = 0; i < num_channel; j++) {
> +		u32 val = 0;
> +		int k, shift = 0;
> +
> +		if (clear)
> +			i += 4;
> +		else
> +			for (k = 0; k < 4 && i < num_channel; i++, k++, shift += 8)
> +				val |= (i & 0x1f) << shift;
> +
> +		iowrite32(val, &int_regs->channel_msi_vector[j]);
> +	}
> +}
> +
> +static void irq_channel_teardown(struct xdma_dev *xdev, int h2c_channel_max,
> +				 int c2h_channel_max)
> +{
> +	struct xdma_engine *engine;
> +	int i = 0, j = 0;
> +
> +	engine = xdev->engine_h2c;
> +	for (i = 0; i < h2c_channel_max; i++, j++, engine++) {
> +		if (!engine->irq_line)
> +			break;
> +		free_irq(engine->irq_line, engine);
> +	}
> +
> +	engine = xdev->engine_c2h;
> +	for (i = 0; i < c2h_channel_max; i++, j++, engine++) {
> +		if (!engine->irq_line)
> +			break;
> +		free_irq(engine->irq_line, engine);
> +	}
> +}
> +
> +static int irq_channel_setup(struct xdma_dev *xdev, int h2c_channel_max,
> +			     int c2h_channel_max)
> +{
> +	int i, j, rv;
> +	u32 vector;
> +	struct xdma_engine *engine;
> +
> +	j = h2c_channel_max;
> +	engine = xdev->engine_h2c;
> +	for (i = 0; i < h2c_channel_max; i++, engine++) {
> +		vector = pci_irq_vector(xdev->pdev, i);
> +		rv = request_irq(vector, xdma_isr, 0, engine->name, engine);
> +		if (rv) {
> +			pr_err("XDMA: %s: error requesting irq#%d\n",
> +			       engine->name, vector);
> +			return rv;
> +		}
> +		pr_info("XDMA: %s: irq#%d\n", engine->name, vector);
> +		engine->irq_line = vector;
> +	}
> +
> +	engine = xdev->engine_c2h;
> +	for (i = 0; i < c2h_channel_max; i++, j++, engine++) {
> +		vector = pci_irq_vector(xdev->pdev, j);
> +		rv = request_irq(vector, xdma_isr, 0, engine->name, engine);
> +		if (rv) {
> +			pr_err("XDMA: %s: error requesting irq#%d\n",
> +			       engine->name, vector);
> +			return rv;
> +		}
> +		pr_info("XDMA: %s: irq#%d\n", engine->name, vector);
> +		engine->irq_line = vector;
> +	}
> +
> +	return 0;
> +}
> +
> +static void irq_teardown(struct xdma_dev *xdev, int h2c_channel_max,
> +			 int c2h_channel_max, int user_irq_max)
> +{
> +	int num_channel = h2c_channel_max + c2h_channel_max;
> +
> +	prog_irq_user(xdev, num_channel, user_irq_max, 1);
> +	prog_irq_channel(xdev, num_channel, 1);
> +
> +	irq_channel_teardown(xdev, h2c_channel_max, c2h_channel_max);
> +}
> +
> +static int irq_setup(struct xdma_dev *xdev, int h2c_channel_max,
> +		     int c2h_channel_max, int user_irq_max)
> +{
> +	int rv;
> +	int num_channel = h2c_channel_max + c2h_channel_max;
> +
> +	rv = irq_channel_setup(xdev, h2c_channel_max, c2h_channel_max);
> +	if (rv)
> +		return rv;
> +
> +	prog_irq_channel(xdev, num_channel, 0);
> +	prog_irq_user(xdev, num_channel, user_irq_max, 0);
> +
> +	return 0;
> +}
> +
> +/* Chains the descriptors as a singly-linked list
> + *
> + * Each descriptor's next pointer specifies the bus address of the next
> + * descriptor.
> + * Terminates the last descriptor to form a singly-linked list.
> + */
> +static void transfer_desc_init(struct xdma_transfer *transfer, int count)
> +{
> +	struct xdma_desc *desc_virt = transfer->desc_virt;
> +	dma_addr_t desc_bus = transfer->desc_bus;
> +	int i;
> +
> +	BUG_ON(count > XDMA_TRANSFER_MAX_DESC);
> +
> +	/* create singly-linked list for SG DMA controller */
> +	for (i = 0; i < count - 1; i++) {
> +		/* increment bus address to next in array */
> +		desc_bus += sizeof(struct xdma_desc);
> +
> +		/* singly-linked list uses bus addresses */
> +		desc_virt[i].next_lo = cpu_to_le32(PCI_DMA_L(desc_bus));
> +		desc_virt[i].next_hi = cpu_to_le32(PCI_DMA_H(desc_bus));
> +		desc_virt[i].bytes = cpu_to_le32(0);
> +
> +		desc_virt[i].control = cpu_to_le32(DESC_MAGIC);
> +	}
> +
> +	/* zero the last descriptor next pointer */
> +	desc_virt[i].next_lo = cpu_to_le32(0);
> +	desc_virt[i].next_hi = cpu_to_le32(0);
> +	desc_virt[i].bytes = cpu_to_le32(0);
> +	desc_virt[i].control = cpu_to_le32(DESC_MAGIC);
> +}
> +
> +/* Set how many descriptors are adjacent to this one */
> +static void xdma_desc_adjacent(struct xdma_desc *desc, u32 next_adjacent)
> +{
> +	/* remember reserved and control bits */
> +	u32 control = le32_to_cpu(desc->control) & 0x0000f0ffUL;
> +	/* merge adjacent and control field */
> +	control |= 0xAD4B0000UL | (next_adjacent << 8);
> +	/* write control and next_adjacent */
> +	desc->control = cpu_to_le32(control);
> +}
> +
> +/* Set complete control field of a descriptor */
> +static void xdma_desc_control_set(struct xdma_desc *first, u32 control_field)
> +{
> +	/* remember magic and adjacent number */
> +	u32 control = le32_to_cpu(first->control) & ~(LS_BYTE_MASK);
> +
> +	/* merge adjacent and control field */
> +	control |= control_field;
> +	/* write control and next_adjacent */
> +	first->control = cpu_to_le32(control);
> +}
> +
> +static inline void xdma_desc_done(struct xdma_desc *desc_virt, int count)
> +{
> +	memset(desc_virt, 0, count * sizeof(struct xdma_desc));
> +}
> +
> +/* Fill a descriptor with the transfer details */
> +static void xdma_desc_set(struct xdma_desc *desc, dma_addr_t rc_bus_addr,
> +			  u64 ep_addr, int len, int dir)
> +{
> +	/* transfer length */
> +	desc->bytes = cpu_to_le32(len);
> +	if (dir == DMA_TO_DEVICE) {
> +		/* read from root complex memory (source address) */
> +		desc->src_addr_lo = cpu_to_le32(PCI_DMA_L(rc_bus_addr));
> +		desc->src_addr_hi = cpu_to_le32(PCI_DMA_H(rc_bus_addr));
> +		/* write to end point address (destination address) */
> +		desc->dst_addr_lo = cpu_to_le32(PCI_DMA_L(ep_addr));
> +		desc->dst_addr_hi = cpu_to_le32(PCI_DMA_H(ep_addr));
> +	} else {
> +		/* read from end point address (source address) */
> +		desc->src_addr_lo = cpu_to_le32(PCI_DMA_L(ep_addr));
> +		desc->src_addr_hi = cpu_to_le32(PCI_DMA_H(ep_addr));
> +		/* write to root complex memory (destination address) */
> +		desc->dst_addr_lo = cpu_to_le32(PCI_DMA_L(rc_bus_addr));
> +		desc->dst_addr_hi = cpu_to_le32(PCI_DMA_H(rc_bus_addr));
> +	}
> +}
> +
> +static void transfer_abort(struct xdma_engine *engine,
> +			  struct xdma_transfer *transfer)
> +{
> +	struct xdma_transfer *head;
> +
> +	head = list_entry(engine->transfer_list.next, struct xdma_transfer,
> +			  entry);
> +	if (head == transfer)
> +		list_del(engine->transfer_list.next);
> +	else
> +		pr_warn("XDMA: %s: transfer for abort NOT found\n",
> +			engine->name);
> +
> +	if (transfer->state == TRANSFER_STATE_SUBMITTED)
> +		transfer->state = TRANSFER_STATE_ABORTED;
> +}
> +
> +static int transfer_queue(struct xdma_engine *engine,
> +			  struct xdma_transfer *transfer)
> +{
> +	int rv = 0;
> +	unsigned long flags;
> +
> +	/* lock the engine state */
> +	spin_lock_irqsave(&engine->lock, flags);
> +
> +	engine->prev_cpu = get_cpu();
> +	put_cpu();
> +
> +	/* engine is being shutdown; do not accept new transfers */
> +	if (engine->shutdown & ENGINE_SHUTDOWN_REQUEST) {
> +		pr_info("XDMA: %s: engine offline, transfer not queued\n",
> +			engine->name);
> +		rv = -EBUSY;
> +		goto shutdown;
> +	}
> +
> +	/* mark the transfer as submitted */
> +	transfer->state = TRANSFER_STATE_SUBMITTED;
> +	/* add transfer to the tail of the engine transfer queue */
> +	list_add_tail(&transfer->entry, &engine->transfer_list);
> +
> +	if (!engine->running)
> +		rv = engine_start(engine);
> +
> +shutdown:
> +	spin_unlock_irqrestore(&engine->lock, flags);
> +
> +	return rv;
> +}
> +
> +static void engine_alignments(struct xdma_engine *engine)
> +{
> +	u32 w = ioread32(&engine->regs->alignments);
> +
> +	if (w) {
> +		engine->addr_align = (w & 0x00ff0000U) >> 16;
> +		engine->len_granularity = (w & 0x0000ff00U) >> 8;
> +		engine->addr_bits = (w & 0x000000ffU);
> +	} else {
> +		/* Some default values if alignments are unspecified */
> +		engine->addr_align = 1;
> +		engine->len_granularity = 1;
> +		engine->addr_bits = 64;
> +	}
> +}
> +
> +static void engine_free_resource(struct xdma_engine *engine)
> +{
> +	struct xdma_dev *xdev = engine->xdev;
> +
> +	if (engine->desc) {
> +		dma_free_coherent(&xdev->pdev->dev,
> +				  XDMA_TRANSFER_MAX_DESC *
> +					  sizeof(struct xdma_desc),
> +				  engine->desc, engine->desc_bus);
> +		engine->desc = NULL;
> +	}
> +
> +	if (engine->cyclic_result) {
> +		dma_free_coherent(
> +			&xdev->pdev->dev,
> +			XDMA_TRANSFER_MAX_DESC * sizeof(struct xdma_result),
> +			engine->cyclic_result, engine->cyclic_result_bus);
> +		engine->cyclic_result = NULL;
> +	}
> +}
> +
> +static void engine_destroy(struct xdma_dev *xdev, struct xdma_engine *engine)
> +{
> +	/* Disable interrupts to stop processing new events during shutdown */
> +	iowrite32(0x0, &engine->regs->interrupt_enable_mask);
> +
> +	if (enable_credit_mp && engine->streaming &&
> +	    engine->dir == DMA_FROM_DEVICE) {
> +		u32 reg_value = (0x1 << engine->channel) << 16;
> +		struct sgdma_common_regs *reg =
> +			(struct sgdma_common_regs *)
> +			(xdev->config_bar + (0x6 * TARGET_SPACING));
> +		iowrite32(reg_value, &reg->credit_mode_enable_w1c);
> +	}
> +
> +	/* Release memory use for descriptor writebacks */
> +	engine_free_resource(engine);
> +
> +	memset(engine, 0, sizeof(struct xdma_engine));
> +	/* Decrement the number of engines available */
> +	xdev->engines_num--;
> +}
> +
> +static void engine_init_regs(struct xdma_engine *engine)
> +{
> +	u32 reg_value;
> +
> +	iowrite32(XDMA_CTRL_NON_INCR_ADDR, &engine->regs->control_w1c);
> +
> +	engine_alignments(engine);
> +
> +	/* Configure error interrupts by default */
> +	reg_value = XDMA_CTRL_IE_DESC_ALIGN_MISMATCH;
> +	reg_value |= XDMA_CTRL_IE_MAGIC_STOPPED;
> +	reg_value |= XDMA_CTRL_IE_MAGIC_STOPPED;
> +	reg_value |= XDMA_CTRL_IE_READ_ERROR;
> +	reg_value |= XDMA_CTRL_IE_DESC_ERROR;
> +
> +	/* enable the relevant completion interrupts */
> +	reg_value |= XDMA_CTRL_IE_DESC_STOPPED;
> +	reg_value |= XDMA_CTRL_IE_DESC_COMPLETED;
> +
> +	/* Apply engine configurations */
> +	iowrite32(reg_value, &engine->regs->interrupt_enable_mask);
> +
> +	engine->interrupt_enable_mask_value = reg_value;
> +
> +	/* only enable credit mode for AXI-ST C2H */
> +	if (enable_credit_mp && engine->streaming &&
> +	    engine->dir == DMA_FROM_DEVICE) {
> +		struct xdma_dev *xdev = engine->xdev;
> +		u32 reg_value = (0x1 << engine->channel) << 16;
> +		struct sgdma_common_regs *reg =
> +			(struct sgdma_common_regs *)
> +			(xdev->config_bar + (0x6 * TARGET_SPACING));
> +
> +		iowrite32(reg_value, &reg->credit_mode_enable_w1s);
> +	}
> +}
> +
> +static int engine_alloc_resource(struct xdma_engine *engine)
> +{
> +	struct xdma_dev *xdev = engine->xdev;
> +
> +	engine->desc = dma_alloc_coherent(&xdev->pdev->dev,
> +					  XDMA_TRANSFER_MAX_DESC *
> +						  sizeof(struct xdma_desc),
> +					  &engine->desc_bus, GFP_KERNEL);
> +	if (!engine->desc)
> +		goto err_out;
> +
> +	if (engine->streaming && engine->dir == DMA_FROM_DEVICE) {
> +		engine->cyclic_result = dma_alloc_coherent(
> +			&xdev->pdev->dev,
> +			XDMA_TRANSFER_MAX_DESC * sizeof(struct xdma_result),
> +			&engine->cyclic_result_bus, GFP_KERNEL);
> +
> +		if (!engine->cyclic_result)
> +			goto err_out;
> +	}
> +
> +	return 0;
> +
> +err_out:
> +	engine_free_resource(engine);
> +	return -ENOMEM;
> +}
> +
> +static int engine_init(struct xdma_engine *engine, struct xdma_dev *xdev,
> +		       int offset, enum dma_data_direction dir, int channel)
> +{
> +	int rv;
> +	u32 val;
> +
> +	engine->channel = channel;
> +	engine->xdev = xdev;
> +
> +	/* engine interrupt request bit */
> +	engine->irq_bitmask = (1 << XDMA_ENG_IRQ_NUM) - 1;
> +	engine->irq_bitmask <<= (xdev->engines_num * XDMA_ENG_IRQ_NUM);
> +
> +	/* register address */
> +	engine->regs = xdev->config_bar + offset;
> +	engine->sgdma_regs = xdev->config_bar + offset +
> +			     SGDMA_OFFSET_FROM_CHANNEL;
> +	val = ioread32(&engine->regs->identifier);
> +	if (val & 0x8000U)
> +		engine->streaming = 1;
> +
> +	/* remember SG DMA direction */
> +	engine->dir = dir;
> +	sprintf(engine->name, "xdma-%s%d%s", (dir == DMA_TO_DEVICE) ? "H2C" : "C2H",
> +		channel, engine->streaming ? "ST" : "MM");
> +
> +	/* initialize the deferred work for transfer completion */
> +	INIT_WORK(&engine->work, engine_service_work);
> +
> +	xdev->engines_num++;
> +
> +	rv = engine_alloc_resource(engine);
> +	if (rv)
> +		return rv;
> +	engine_init_regs(engine);
> +
> +	return 0;
> +}
> +
> +static void transfer_destroy(struct xdma_dev *xdev, struct xdma_transfer *xfer)
> +{
> +	xdma_desc_done(xfer->desc_virt, xfer->desc_num);
> +
> +	if (xfer->last_in_request && (xfer->flags & XFER_FLAG_NEED_UNMAP)) {
> +		struct sg_table *sgt = xfer->sgt;
> +
> +		if (sgt->nents) {
> +			dma_unmap_sg(&xdev->pdev->dev, sgt->sgl, sgt->nents,
> +				     xfer->dir);
> +			sgt->nents = 0;
> +		}
> +	}
> +}
> +
> +static void transfer_build(struct xdma_engine *engine,
> +			struct xdma_request_cb *req, struct xdma_transfer *xfer,
> +			unsigned int desc_max)
> +{
> +	struct sw_desc *sdesc = &(req->sdesc[req->sw_desc_idx]);
> +	int i, j;
> +	dma_addr_t bus = xfer->res_bus;
> +
> +	for (i = 0, j = 0; i < desc_max; i++, j++, sdesc++) {
> +		/* fill in descriptor entry j with transfer details */
> +		xdma_desc_set(xfer->desc_virt + j, sdesc->addr, req->ep_addr,
> +			      sdesc->len, xfer->dir);
> +		xfer->len += sdesc->len;
> +
> +		/* for non-inc-add mode don't increment ep_addr */
> +		if (!engine->non_incr_addr)
> +			req->ep_addr += sdesc->len;
> +
> +		if (engine->streaming && engine->dir == DMA_FROM_DEVICE) {
> +			memset(xfer->res_virt + j, 0,
> +				sizeof(struct xdma_result));
> +			xfer->desc_virt[j].src_addr_lo =
> +						cpu_to_le32(PCI_DMA_L(bus));
> +			xfer->desc_virt[j].src_addr_hi =
> +						cpu_to_le32(PCI_DMA_H(bus));
> +			bus += sizeof(struct xdma_result);
> +		}
> +
> +	}
> +
> +	req->sw_desc_idx += desc_max;
> +}
> +
> +static void transfer_init(struct xdma_engine *engine,
> +			struct xdma_request_cb *req, struct xdma_transfer *xfer)
> +{
> +	unsigned int desc_max = min_t(unsigned int,
> +				req->sw_desc_cnt - req->sw_desc_idx,
> +				XDMA_TRANSFER_MAX_DESC);
> +	int i, last;
> +	u32 control;
> +	unsigned long flags;
> +
> +	memset(xfer, 0, sizeof(*xfer));
> +
> +	spin_lock_irqsave(&engine->lock, flags);
> +	init_swait_queue_head(&xfer->wq);
> +
> +	/* remember direction of transfer */
> +	xfer->dir = engine->dir;
> +	xfer->desc_virt = engine->desc + engine->desc_idx;
> +	xfer->res_virt = engine->cyclic_result + engine->desc_idx;
> +	xfer->desc_bus = engine->desc_bus +
> +			(sizeof(struct xdma_desc) * engine->desc_idx);
> +	xfer->res_bus = engine->cyclic_result_bus +
> +			(sizeof(struct xdma_result) * engine->desc_idx);
> +	xfer->desc_index = engine->desc_idx;
> +
> +	if ((engine->desc_idx + desc_max) >= XDMA_TRANSFER_MAX_DESC)
> +		desc_max = XDMA_TRANSFER_MAX_DESC - engine->desc_idx;
> +
> +	transfer_desc_init(xfer, desc_max);
> +	transfer_build(engine, req, xfer, desc_max);
> +
> +	xfer->desc_adjacent = desc_max;
> +
> +	/* terminate last descriptor */
> +	last = desc_max - 1;
> +	/* stop engine, EOP for AXI ST, req IRQ on last descriptor */
> +	control = XDMA_DESC_STOPPED;
> +	control |= XDMA_DESC_EOP;
> +	control |= XDMA_DESC_COMPLETED;
> +	xdma_desc_control_set(xfer->desc_virt + last, control);
> +
> +	if (engine->eop_flush) {
> +		for (i = 0; i < last; i++)
> +			xdma_desc_control_set(xfer->desc_virt + i,
> +					XDMA_DESC_COMPLETED);
> +		xfer->desc_cmpl_th = 1;
> +	} else
> +		xfer->desc_cmpl_th = desc_max;
> +
> +	xfer->desc_num = desc_max;
> +	engine->desc_idx = (engine->desc_idx + desc_max) % XDMA_TRANSFER_MAX_DESC;
> +	engine->desc_used += desc_max;
> +
> +	/* fill in adjacent numbers */
> +	for (i = 0; i < xfer->desc_num; i++) {
> +		u32 next_adj = xdma_get_next_adj(xfer->desc_num - i - 1,
> +						(xfer->desc_virt + i)->next_lo);
> +		xdma_desc_adjacent(xfer->desc_virt + i, next_adj);
> +	}
> +
> +	spin_unlock_irqrestore(&engine->lock, flags);
> +}
> +
> +static void xdma_request_free(struct xdma_request_cb *req)
> +{
> +	kvfree(req);
> +}
> +
> +static struct xdma_request_cb *xdma_request_alloc(struct xdma_dev *xdev,
> +						  unsigned int sdesc_nr)
> +{
> +	unsigned int size = sizeof(struct xdma_request_cb) +
> +			    sdesc_nr * sizeof(struct sw_desc);
> +
> +	return kvzalloc(size, GFP_KERNEL);
> +}
> +
> +static struct xdma_request_cb *xdma_init_request(struct xdma_dev *xdev,
> +						 struct sg_table *sgt,
> +						 u64 ep_addr)
> +{
> +	struct xdma_request_cb *req;
> +	struct scatterlist *sg = sgt->sgl;
> +	int max = sgt->nents;
> +	int extra = 0;
> +	int i, j = 0;
> +
> +	for (i = 0; i < max; i++, sg = sg_next(sg)) {
> +		unsigned int len = sg_dma_len(sg);
> +
> +		if (unlikely(len > XDMA_DESC_BLEN_MAX))
> +			extra += (len + XDMA_DESC_BLEN_MAX - 1) / XDMA_DESC_BLEN_MAX;
> +	}
> +
> +	max += extra;
> +	req = xdma_request_alloc(xdev, max);
> +	if (!req)
> +		return NULL;
> +
> +	req->sgt = sgt;
> +	req->ep_addr = ep_addr;
> +
> +	for (i = 0, sg = sgt->sgl; i < sgt->nents; i++, sg = sg_next(sg)) {
> +		unsigned int tlen = sg_dma_len(sg);
> +		dma_addr_t addr = sg_dma_address(sg);
> +
> +		req->total_len += tlen;
> +		while (tlen) {
> +			req->sdesc[j].addr = addr;
> +			if (tlen > XDMA_DESC_BLEN_MAX) {
> +				req->sdesc[j].len = XDMA_DESC_BLEN_MAX;
> +				addr += XDMA_DESC_BLEN_MAX;
> +				tlen -= XDMA_DESC_BLEN_MAX;
> +			} else {
> +				req->sdesc[j].len = tlen;
> +				tlen = 0;
> +			}
> +			j++;
> +		}
> +	}
> +
> +	if (j > max) {
> +		pr_err("XDMA: Max. transfer length (%d) exceeded",
> +		       XDMA_DESC_BLEN_MAX);
> +		xdma_request_free(req);
> +		return NULL;
> +	}
> +	req->sw_desc_cnt = j;
> +
> +	return req;
> +}
> +
> +static struct xdma_engine *channel_engine(struct xdma_core *xdma, int channel,
> +					  bool write)
> +{
> +	if (write) {
> +		if (channel >= xdma->h2c_channel_max) {
> +			pr_err("XDMA: %d: invalid H2C channel\n", channel);
> +			return NULL;
> +		} else
> +			return &xdma->xdev->engine_h2c[channel];
> +	} else {
> +		if (channel >= xdma->c2h_channel_max) {
> +			pr_err("XDMA: %d: invalid C2H channel\n", channel);
> +			return NULL;
> +		} else
> +			return &xdma->xdev->engine_c2h[channel];
> +	}
> +}
> +
> +static struct xdma_dev *alloc_dev(struct pci_dev *pdev)
> +{
> +	int i;
> +	struct xdma_dev *xdev;
> +	struct xdma_engine *engine;
> +
> +	xdev = kzalloc(sizeof(struct xdma_dev), GFP_KERNEL);
> +	if (!xdev)
> +		return NULL;
> +
> +	xdev->pdev = pdev;
> +
> +	engine = xdev->engine_h2c;
> +	for (i = 0; i < XDMA_CHANNEL_NUM_MAX; i++, engine++) {
> +		spin_lock_init(&engine->lock);
> +		mutex_init(&engine->desc_lock);
> +		INIT_LIST_HEAD(&engine->transfer_list);
> +		init_swait_queue_head(&engine->shutdown_wq);
> +	}
> +
> +	engine = xdev->engine_c2h;
> +	for (i = 0; i < XDMA_CHANNEL_NUM_MAX; i++, engine++) {
> +		spin_lock_init(&engine->lock);
> +		mutex_init(&engine->desc_lock);
> +		INIT_LIST_HEAD(&engine->transfer_list);
> +		init_swait_queue_head(&engine->shutdown_wq);
> +	}
> +
> +	return xdev;
> +}
> +
> +static int set_dma_mask(struct xdma_dev *xdev)
> +{
> +	if (!dma_set_mask(&xdev->pdev->dev, DMA_BIT_MASK(64))) {
> +		pr_devel("XDMA: Using a 64-bit DMA mask\n");
> +		/* use 32-bit DMA for descriptors */
> +		dma_set_coherent_mask(&xdev->pdev->dev, DMA_BIT_MASK(32));
> +	} else if (!dma_set_mask(&xdev->pdev->dev, DMA_BIT_MASK(32))) {
> +		pr_devel("XDMA: Using a 32-bit DMA mask\n");
> +		dma_set_coherent_mask(&xdev->pdev->dev, DMA_BIT_MASK(32));
> +	} else {
> +		pr_err("XDMA: No suitable DMA possible.\n");
> +		return -EINVAL;
> +	}
> +
> +	return 0;
> +}
> +
> +static int get_engine_channel_id(struct engine_regs *regs)
> +{
> +	int value = ioread32(&regs->identifier);
> +
> +	return (value & 0x00000f00U) >> 8;
> +}
> +
> +static int get_engine_id(struct engine_regs *regs)
> +{
> +	int value = ioread32(&regs->identifier);
> +
> +	return (value & 0xffff0000U) >> 16;
> +}
> +
> +static void remove_engines(struct xdma_dev *xdev, int h2c_channel_max,
> +			   int c2h_channel_max)
> +{
> +	int i;
> +
> +	for (i = 0; i < h2c_channel_max; i++)
> +		engine_destroy(xdev, &xdev->engine_h2c[i]);
> +
> +	for (i = 0; i < c2h_channel_max; i++)
> +		engine_destroy(xdev, &xdev->engine_c2h[i]);
> +}
> +
> +static int probe_for_engine(struct xdma_dev *xdev, enum dma_data_direction dir,
> +			    int channel)
> +{
> +	struct engine_regs *regs;
> +	int offset = channel * CHANNEL_SPACING;
> +	u32 engine_id;
> +	u32 engine_id_expected;
> +	u32 channel_id;
> +	struct xdma_engine *engine;
> +
> +	if (dir == DMA_TO_DEVICE) {
> +		engine_id_expected = XDMA_ID_H2C;
> +		engine = &xdev->engine_h2c[channel];
> +	} else {
> +		offset += H2C_CHANNEL_OFFSET;
> +		engine_id_expected = XDMA_ID_C2H;
> +		engine = &xdev->engine_c2h[channel];
> +	}
> +
> +	regs = xdev->config_bar + offset;
> +	engine_id = get_engine_id(regs);
> +	channel_id = get_engine_channel_id(regs);
> +
> +	if ((engine_id != engine_id_expected) || (channel_id != channel)) {
> +		pr_err("XDMA: %s engine #%d not found\n",
> +		       dir == DMA_TO_DEVICE ? "H2C" : "C2H", channel);
> +		return -EINVAL;
> +	}
> +
> +	engine_init(engine, xdev, offset, dir, channel);
> +
> +	return 0;
> +}
> +
> +static int probe_engines(struct xdma_dev *xdev, int h2c_channel_max,
> +			 int c2h_channel_max)
> +{
> +	int i, rv;
> +
> +	for (i = 0; i < h2c_channel_max; i++) {
> +		rv = probe_for_engine(xdev, DMA_TO_DEVICE, i);
> +		if (rv)
> +			return rv;
> +	}
> +
> +	for (i = 0; i < c2h_channel_max; i++) {
> +		rv = probe_for_engine(xdev, DMA_FROM_DEVICE, i);
> +		if (rv)
> +			return rv;
> +	}
> +
> +	return 0;
> +}
> +
> +
> +int xdma_probe(struct xdma_core *xdma)
> +{
> +	int rv;
> +
> +	if (xdma->user_irq_max > MAX_USER_IRQ) {
> +		pr_err("XDMA: %d: Invalid number of user IRQs\n",
> +		       xdma->user_irq_max);
> +		return -EINVAL;
> +	}
> +	if (xdma->h2c_channel_max > XDMA_CHANNEL_NUM_MAX) {
> +		pr_err("XDMA: %d: Invalid number of H2C channels\n",
> +		       xdma->h2c_channel_max);
> +		return -EINVAL;
> +	}
> +	if (xdma->c2h_channel_max > XDMA_CHANNEL_NUM_MAX) {
> +		pr_err("XDMA: %d: Invalid number of C2H channels\n",
> +		       xdma->c2h_channel_max);
> +		return -EINVAL;
> +	}
> +
> +	xdma->xdev = alloc_dev(xdma->pdev);
> +	if (!xdma->xdev)
> +		return -ENOMEM;
> +
> +	rv = map_config_bar(xdma->xdev, xdma->config_bar_id);
> +	if (rv)
> +		goto err_map;
> +
> +	rv = set_dma_mask(xdma->xdev);
> +	if (rv)
> +		goto err_mask;
> +
> +	channel_interrupts_disable(xdma->xdev, ~0);
> +	user_interrupts_disable(xdma->xdev, ~0);
> +	/* Flush writes */
> +	read_interrupts(xdma->xdev);
> +
> +	rv = probe_engines(xdma->xdev, xdma->h2c_channel_max, xdma->c2h_channel_max);
> +	if (rv)
> +		goto err_engines;
> +
> +	rv = irq_setup(xdma->xdev, xdma->h2c_channel_max, xdma->c2h_channel_max,
> +		       xdma->user_irq_max);
> +	if (rv < 0)
> +		goto err_interrupts;
> +	channel_interrupts_enable(xdma->xdev, ~0);
> +	/* Flush writes */
> +	read_interrupts(xdma->xdev);
> +
> +	return 0;
> +
> +err_interrupts:
> +	irq_teardown(xdma->xdev, xdma->h2c_channel_max, xdma->c2h_channel_max,
> +		     xdma->user_irq_max);
> +err_engines:
> +	remove_engines(xdma->xdev, xdma->h2c_channel_max, xdma->c2h_channel_max);
> +err_mask:
> +	unmap_config_bar(xdma->xdev, xdma->config_bar_id);
> +err_map:
> +	kfree(xdma->xdev);
> +
> +	return rv;
> +}
> +EXPORT_SYMBOL_GPL(xdma_probe);
> +
> +void xdma_remove(struct xdma_core *xdma)
> +{
> +	channel_interrupts_disable(xdma->xdev, ~0);
> +	user_interrupts_disable(xdma->xdev, ~0);
> +	/* Flush writes */
> +	read_interrupts(xdma->xdev);
> +
> +	irq_teardown(xdma->xdev, xdma->h2c_channel_max, xdma->c2h_channel_max,
> +		     xdma->user_irq_max);
> +
> +	remove_engines(xdma->xdev, xdma->h2c_channel_max, xdma->c2h_channel_max);
> +	unmap_config_bar(xdma->xdev, xdma->config_bar_id);
> +
> +	kfree(xdma->xdev);
> +}
> +EXPORT_SYMBOL_GPL(xdma_remove);
> +
> +/**
> + * xdma_irq_enable - enable XDMA user interrupt(s)
> + * @xdma: XDMA device handle
> + * @mask: bitmask of user interrupts (0 ~ 15) to be registered
> + */
> +void xdma_irq_enable(struct xdma_core *xdma, unsigned int mask)
> +{
> +	xdma->xdev->mask_irq_user |= mask;
> +	user_interrupts_enable(xdma->xdev, mask);
> +	/* Flush writes */
> +	read_interrupts(xdma->xdev);
> +}
> +EXPORT_SYMBOL_GPL(xdma_irq_enable);
> +
> +/**
> + * xdma_irq_disable - disable XDMA user interrupt(s)
> + * @xdma: XDMA device handle
> + * @mask: bitmask of user interrupts (0 ~ 15) to be unregistered
> + */
> +void xdma_irq_disable(struct xdma_core *xdma, unsigned int mask)
> +{
> +	xdma->xdev->mask_irq_user &= ~mask;
> +	user_interrupts_disable(xdma->xdev, mask);
> +	/* Flush writes */
> +	read_interrupts(xdma->xdev);
> +}
> +EXPORT_SYMBOL_GPL(xdma_irq_disable);
> +
> +/**
> + * xdma_transfer - do a DMA transfer
> + * @xdma: XDMA device handle
> + * @channel: channel number
> + * @write: slecets read/write operation
> + * @ep_addr: offset into the DDR/BRAM (card) memory to read from or write to
> + * @sg_table: the scatter-gather list of data buffers
> + * @timeout_ms: timeout in mili-seconds
> + *
> + * Returns # of bytes transferred on success, negative on failure
> + */
> +ssize_t xdma_transfer(struct xdma_core *xdma, int channel, bool write,
> +		      u64 ep_addr, struct sg_table *sgt, int timeout_ms)
> +{
> +	struct xdma_engine *engine;
> +	int rv = 0, i, nents;
> +	ssize_t done = 0;
> +	struct xdma_request_cb *req = NULL;
> +
> +
> +	engine = channel_engine(xdma, channel, write);
> +	if (!engine)
> +		return -EINVAL;
> +
> +	req = xdma_init_request(xdma->xdev, sgt, ep_addr);
> +	if (!req)
> +		return -ENOMEM;
> +
> +	nents = req->sw_desc_cnt;
> +	mutex_lock(&engine->desc_lock);
> +
> +	while (nents) {
> +		unsigned long flags;
> +		struct xdma_transfer *xfer;
> +
> +		/* build transfer */
> +		transfer_init(engine, req, &req->tfer);
> +		xfer = &req->tfer;
> +
> +		/* last transfer for the given request? */
> +		nents -= xfer->desc_num;
> +		if (!nents) {
> +			xfer->last_in_request = 1;
> +			xfer->sgt = sgt;
> +		}
> +
> +		rv = transfer_queue(engine, xfer);
> +		if (rv < 0)
> +			break;
> +
> +		if (timeout_ms > 0)
> +			swait_event_interruptible_timeout_exclusive(xfer->wq,
> +				(xfer->state != TRANSFER_STATE_SUBMITTED),
> +				msecs_to_jiffies(timeout_ms));
> +		else
> +			swait_event_interruptible_exclusive(xfer->wq,
> +				(xfer->state != TRANSFER_STATE_SUBMITTED));
> +
> +		spin_lock_irqsave(&engine->lock, flags);
> +
> +		switch (xfer->state) {
> +		case TRANSFER_STATE_COMPLETED:
> +			spin_unlock_irqrestore(&engine->lock, flags);
> +			/* For C2H streaming use writeback results */
> +			if (engine->streaming &&
> +			    engine->dir == DMA_FROM_DEVICE) {
> +				struct xdma_result *result = xfer->res_virt;
> +
> +				for (i = 0; i < xfer->desc_cmpl; i++)
> +					done += result[i].length;
> +
> +				/* finish the whole request */
> +				if (engine->eop_flush)
> +					nents = 0;
> +			} else
> +				done += xfer->len;
> +			rv = 0;
> +			break;
> +		case TRANSFER_STATE_FAILED:
> +			pr_warn("XDMA: transfer failed\n");
> +			spin_unlock_irqrestore(&engine->lock, flags);
> +			rv = -EIO;
> +			break;
> +		default:
> +			/* transfer can still be in-flight */
> +			pr_warn("XDMA: transfer timed out\n");
> +			engine_status_read(engine, 0, 1);
> +			transfer_abort(engine, xfer);
> +			engine_stop(engine);
> +			spin_unlock_irqrestore(&engine->lock, flags);
> +			rv = -ERESTARTSYS;
> +			break;
> +		}
> +
> +		engine->desc_used -= xfer->desc_num;
> +		transfer_destroy(xdma->xdev, xfer);
> +
> +		if (rv < 0)
> +			break;
> +	}
> +
> +	mutex_unlock(&engine->desc_lock);
> +	xdma_request_free(req);
> +
> +	return rv ? rv : done;
> +}
> +EXPORT_SYMBOL_GPL(xdma_transfer);
> +
> +MODULE_AUTHOR("Digiteq Automotive s.r.o.");
> +MODULE_DESCRIPTION("Xilinx XDMA Driver");
> +MODULE_LICENSE("GPL");
> diff --git a/include/linux/dma/xilinx_xdma.h b/include/linux/dma/xilinx_xdma.h
> new file mode 100644
> index 000000000000..c63dc7768e66
> --- /dev/null
> +++ b/include/linux/dma/xilinx_xdma.h
> @@ -0,0 +1,44 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/*
> + * This file is part of the Xilinx DMA IP Core driver for Linux
> + *
> + * Copyright (c) 2016-2021,  Xilinx, Inc.
> + * Copyright (c) 2020-2022,  Digiteq Automotive s.r.o.
> + */
> +
> +#ifndef XILINX_XDMA_H
> +#define XILINX_XDMA_H
> +
> +#include <linux/pci.h>
> +#include <linux/scatterlist.h>
> +
> +struct xdma_dev;
> +
> +/**
> + * struct xdma_core - representation of XDMA hardware
> + * @pdev:		The parent PCIe device which contains the XDMA core
> + * @config_bar_id:	PCI BAR id where XDMA config regs are located
> + * @user_irq_max:	number of user IRQs
> + * @c2h_channel_max:	number of C2H DMA channels
> + * @h2c_channel_max:	number of H2C DMA channels
> + * @xdev:		struct xdma_dev that is filed by ->probe()
> + */
> +struct xdma_core {
> +	struct pci_dev *pdev;
> +	int config_bar_id;
> +	unsigned int user_irq_max;
> +	unsigned int c2h_channel_max;
> +	unsigned int h2c_channel_max;
> +	struct xdma_dev *xdev;
> +};
> +
> +int xdma_probe(struct xdma_core *xdma);
> +void xdma_remove(struct xdma_core *xdma);
> +
> +void xdma_irq_enable(struct xdma_core *xdma, unsigned int mask);
> +void xdma_irq_disable(struct xdma_core *xdma, unsigned int mask);
> +
> +ssize_t xdma_transfer(struct xdma_core *xdma, int channel, bool write,
> +		      u64 ep_addr, struct sg_table *sgt, int timeout_ms);
> +
> +#endif /* XILINX_XDMA_H */

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH v2 2/3] Added Xilinx XDMA IP core driver
  2022-09-21  6:24   ` Lizhi Hou
@ 2022-09-21  9:25     ` Martin Tůma
  2022-09-21 15:15       ` Lizhi Hou
  0 siblings, 1 reply; 13+ messages in thread
From: Martin Tůma @ 2022-09-21  9:25 UTC (permalink / raw)
  To: lizhi.hou
  Cc: dmaengine, linux-i2c, linux-kernel, linux-media, martin.tuma,
	mchehab, michal.simek, tumic, vkoul

 > Currently, the V3 patch series does not support register user logic
 > interrupt yet.

This is a showstopper for almost every XDMA based PCIe card. As the 
driver "consumes" the whole register space (including the user IRQs 
enable/disable registers), there is AFAIK no way how to enable the user 
IRQs when this driver is loaded.

 > Could you convert your driver to use this?

Not without the user IRQs.

M.

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH v2 0/3] Digiteq Automotive MGB4 driver
  2022-09-21  5:32 ` [PATCH v2 0/3] " Sonal Santan
@ 2022-09-21  9:44   ` Martin Tůma
  0 siblings, 0 replies; 13+ messages in thread
From: Martin Tůma @ 2022-09-21  9:44 UTC (permalink / raw)
  To: sonal.santan
  Cc: dmaengine, linux-i2c, linux-kernel, linux-media, martin.tuma,
	mchehab, michal.simek, tumic, vkoul

Hi,

 > Xilinx/AMD is working on upstreaming the XDMA driver into Linux
 > dmaengine subsystem for use by all users of XDMA IP. You can find the 
 > V3
 > patch set here:
 >
 > 
https://lore.kernel.org/dmaengine/1663631039-49732-1-git-send-email-lizhi.hou@amd.com/T/#t

That's good news. If this would happen before I have spend two weeks 
converting the original Xilinx "driver" to something with at least a 
theoretical chance to be included into the kernel, it would be even 
better... ;-)

 > Will appreciate your review of that patch set and hopefully MGB4 driver
 > can be rebased on top it?

As I already replied to Lizhi Hou, this new XDMA driver is at the moment 
unusable for most XDMA-based HW as it does not handle the user IRQs 
logic and as it also takes up the user IRQs register memory space, it 
even actively hinders other drivers to at least handle the IRQs themselves.

M.

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH v2 2/3] Added Xilinx XDMA IP core driver
  2022-09-21  9:25     ` Martin Tůma
@ 2022-09-21 15:15       ` Lizhi Hou
  2022-09-21 15:35         ` Martin Tůma
  0 siblings, 1 reply; 13+ messages in thread
From: Lizhi Hou @ 2022-09-21 15:15 UTC (permalink / raw)
  To: Martin Tůma
  Cc: dmaengine, linux-i2c, linux-kernel, linux-media, martin.tuma,
	mchehab, michal.simek, vkoul


On 9/21/22 02:25, Martin Tůma wrote:
> > Currently, the V3 patch series does not support register user logic
> > interrupt yet.
>
> This is a showstopper for almost every XDMA based PCIe card. As the 
> driver "consumes" the whole register space (including the user IRQs 
> enable/disable registers), there is AFAIK no way how to enable the 
> user IRQs when this driver is loaded.
>
> > Could you convert your driver to use this?
>
> Not without the user IRQs.

I provided the patch link for user logic IRQ support in previous reply. 
You may pull it and patch it on top of the V3 patch series.


Lizhi

>
> M.

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH v2 2/3] Added Xilinx XDMA IP core driver
  2022-09-21 15:15       ` Lizhi Hou
@ 2022-09-21 15:35         ` Martin Tůma
  0 siblings, 0 replies; 13+ messages in thread
From: Martin Tůma @ 2022-09-21 15:35 UTC (permalink / raw)
  To: Lizhi Hou
  Cc: dmaengine, linux-i2c, linux-kernel, linux-media, martin.tuma,
	mchehab, michal.simek, vkoul

On 21. 09. 22 17:15, Lizhi Hou wrote:
> 
> On 9/21/22 02:25, Martin Tůma wrote:
>> > Currently, the V3 patch series does not support register user logic
>> > interrupt yet.
>>
>> This is a showstopper for almost every XDMA based PCIe card. As the 
>> driver "consumes" the whole register space (including the user IRQs 
>> enable/disable registers), there is AFAIK no way how to enable the 
>> user IRQs when this driver is loaded.
>>
>> > Could you convert your driver to use this?
>>
>> Not without the user IRQs.
> 
> I provided the patch link for user logic IRQ support in previous reply. 
> You may pull it and patch it on top of the V3 patch series.
> 
> 
> Lizhi
> 
>>
>> M.

I'm sorry, I have missed that. With that modification it should be 
(theoretically - haven't tested it yet) possible to use our v4l2 driver 
with this XDMA driver instead of the one I have provided in my patches.

I will now try to rewrite our driver and test it with your XDMA driver 
to be sure it works. It will however definitely need a "V4" of your XDMA 
driver with that patch on github before it can be included into the 
kernel and be usable for all XDMA-based PCIe cards.

M.

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH v2 3/3] Added Digiteq Automotive MGB4 driver
  2022-09-19 18:55 ` [PATCH v2 3/3] Added Digiteq Automotive MGB4 driver tumic
@ 2022-10-03  5:19   ` kernel test robot
  0 siblings, 0 replies; 13+ messages in thread
From: kernel test robot @ 2022-10-03  5:19 UTC (permalink / raw)
  To: tumic, Mauro Carvalho Chehab, Vinod Koul, Michal Simek
  Cc: kbuild-all, linux-media, linux-kernel, dmaengine, linux-i2c,
	Martin Tůma

[-- Attachment #1: Type: text/plain, Size: 1627 bytes --]

Hi,

Thank you for the patch! Perhaps something to improve:

[auto build test WARNING on vkoul-dmaengine/next]
[also build test WARNING on linus/master v6.0]
[cannot apply to media-tree/master xilinx-xlnx/master next-20220930]
[If your patch is applied to the wrong git tree, kindly drop us a note.
And when submitting patch, we suggest to use '--base' as documented in
https://git-scm.com/docs/git-format-patch#_base_tree_information]

url:    https://github.com/intel-lab-lkp/linux/commits/tumic-gpxsee-org/Digiteq-Automotive-MGB4-driver/20220920-021718
base:   https://git.kernel.org/pub/scm/linux/kernel/git/vkoul/dmaengine.git next
reproduce:
        # https://github.com/intel-lab-lkp/linux/commit/097ad9712090ba689cbcf7f5c11d4eac469ed2ca
        git remote add linux-review https://github.com/intel-lab-lkp/linux
        git fetch --no-tags linux-review tumic-gpxsee-org/Digiteq-Automotive-MGB4-driver/20220920-021718
        git checkout 097ad9712090ba689cbcf7f5c11d4eac469ed2ca
        make menuconfig
        # enable CONFIG_COMPILE_TEST, CONFIG_WARN_MISSING_DOCUMENTS, CONFIG_WARN_ABI_ERRORS
        make htmldocs

If you fix the issue, kindly add following tag where applicable
| Reported-by: kernel test robot <lkp@intel.com>

All warnings (new ones prefixed by >>):

>> Documentation/admin-guide/media/mgb4-iio.rst: WARNING: document isn't included in any toctree
>> Documentation/admin-guide/media/mgb4-mtd.rst: WARNING: document isn't included in any toctree
>> Documentation/admin-guide/media/mgb4-sysfs.rst: WARNING: document isn't included in any toctree

-- 
0-DAY CI Kernel Test Service
https://01.org/lkp

[-- Attachment #2: config --]
[-- Type: text/plain, Size: 38516 bytes --]

#
# Automatically generated file; DO NOT EDIT.
# Linux/x86_64 6.0.0-rc1 Kernel Configuration
#
CONFIG_CC_VERSION_TEXT="gcc-11 (Debian 11.3.0-5) 11.3.0"
CONFIG_CC_IS_GCC=y
CONFIG_GCC_VERSION=110300
CONFIG_CLANG_VERSION=0
CONFIG_AS_IS_GNU=y
CONFIG_AS_VERSION=23890
CONFIG_LD_IS_BFD=y
CONFIG_LD_VERSION=23890
CONFIG_LLD_VERSION=0
CONFIG_CC_CAN_LINK=y
CONFIG_CC_CAN_LINK_STATIC=y
CONFIG_CC_HAS_ASM_GOTO=y
CONFIG_CC_HAS_ASM_GOTO_OUTPUT=y
CONFIG_CC_HAS_ASM_INLINE=y
CONFIG_CC_HAS_NO_PROFILE_FN_ATTR=y
CONFIG_PAHOLE_VERSION=123
CONFIG_IRQ_WORK=y
CONFIG_BUILDTIME_TABLE_SORT=y
CONFIG_THREAD_INFO_IN_TASK=y

#
# General setup
#
CONFIG_BROKEN_ON_SMP=y
CONFIG_INIT_ENV_ARG_LIMIT=32
CONFIG_COMPILE_TEST=y
# CONFIG_WERROR is not set
CONFIG_LOCALVERSION=""
CONFIG_BUILD_SALT=""
CONFIG_HAVE_KERNEL_GZIP=y
CONFIG_HAVE_KERNEL_BZIP2=y
CONFIG_HAVE_KERNEL_LZMA=y
CONFIG_HAVE_KERNEL_XZ=y
CONFIG_HAVE_KERNEL_LZO=y
CONFIG_HAVE_KERNEL_LZ4=y
CONFIG_HAVE_KERNEL_ZSTD=y
CONFIG_KERNEL_GZIP=y
# CONFIG_KERNEL_BZIP2 is not set
# CONFIG_KERNEL_LZMA is not set
# CONFIG_KERNEL_XZ is not set
# CONFIG_KERNEL_LZO is not set
# CONFIG_KERNEL_LZ4 is not set
# CONFIG_KERNEL_ZSTD is not set
CONFIG_DEFAULT_INIT=""
CONFIG_DEFAULT_HOSTNAME="(none)"
# CONFIG_SYSVIPC is not set
# CONFIG_WATCH_QUEUE is not set
# CONFIG_CROSS_MEMORY_ATTACH is not set
# CONFIG_USELIB is not set
CONFIG_HAVE_ARCH_AUDITSYSCALL=y

#
# IRQ subsystem
#
CONFIG_GENERIC_IRQ_PROBE=y
CONFIG_GENERIC_IRQ_SHOW=y
CONFIG_HARDIRQS_SW_RESEND=y
CONFIG_IRQ_DOMAIN=y
CONFIG_IRQ_DOMAIN_HIERARCHY=y
CONFIG_GENERIC_IRQ_MATRIX_ALLOCATOR=y
CONFIG_GENERIC_IRQ_RESERVATION_MODE=y
CONFIG_IRQ_FORCED_THREADING=y
CONFIG_SPARSE_IRQ=y
# end of IRQ subsystem

CONFIG_CLOCKSOURCE_WATCHDOG=y
CONFIG_ARCH_CLOCKSOURCE_INIT=y
CONFIG_CLOCKSOURCE_VALIDATE_LAST_CYCLE=y
CONFIG_GENERIC_TIME_VSYSCALL=y
CONFIG_GENERIC_CLOCKEVENTS=y
CONFIG_GENERIC_CLOCKEVENTS_BROADCAST=y
CONFIG_GENERIC_CLOCKEVENTS_MIN_ADJUST=y
CONFIG_GENERIC_CMOS_UPDATE=y
CONFIG_HAVE_POSIX_CPU_TIMERS_TASK_WORK=y
CONFIG_POSIX_CPU_TIMERS_TASK_WORK=y

#
# Timers subsystem
#
CONFIG_HZ_PERIODIC=y
# CONFIG_NO_HZ_IDLE is not set
# CONFIG_NO_HZ is not set
# CONFIG_HIGH_RES_TIMERS is not set
CONFIG_CLOCKSOURCE_WATCHDOG_MAX_SKEW_US=100
# end of Timers subsystem

CONFIG_HAVE_EBPF_JIT=y
CONFIG_ARCH_WANT_DEFAULT_BPF_JIT=y

#
# BPF subsystem
#
# CONFIG_BPF_SYSCALL is not set
# end of BPF subsystem

CONFIG_PREEMPT_NONE_BUILD=y
CONFIG_PREEMPT_NONE=y
# CONFIG_PREEMPT_VOLUNTARY is not set
# CONFIG_PREEMPT is not set
# CONFIG_PREEMPT_DYNAMIC is not set

#
# CPU/Task time and stats accounting
#
CONFIG_TICK_CPU_ACCOUNTING=y
# CONFIG_VIRT_CPU_ACCOUNTING_GEN is not set
# CONFIG_IRQ_TIME_ACCOUNTING is not set
# CONFIG_BSD_PROCESS_ACCT is not set
# CONFIG_PSI is not set
# end of CPU/Task time and stats accounting

CONFIG_CPU_ISOLATION=y

#
# RCU Subsystem
#
CONFIG_TINY_RCU=y
# CONFIG_RCU_EXPERT is not set
CONFIG_SRCU=y
CONFIG_TINY_SRCU=y
# end of RCU Subsystem

# CONFIG_IKCONFIG is not set
# CONFIG_IKHEADERS is not set
CONFIG_LOG_BUF_SHIFT=17
CONFIG_PRINTK_SAFE_LOG_BUF_SHIFT=13
CONFIG_HAVE_UNSTABLE_SCHED_CLOCK=y

#
# Scheduler features
#
# end of Scheduler features

CONFIG_ARCH_SUPPORTS_NUMA_BALANCING=y
CONFIG_ARCH_WANT_BATCHED_UNMAP_TLB_FLUSH=y
CONFIG_CC_HAS_INT128=y
CONFIG_CC_IMPLICIT_FALLTHROUGH="-Wimplicit-fallthrough=5"
CONFIG_GCC12_NO_ARRAY_BOUNDS=y
CONFIG_ARCH_SUPPORTS_INT128=y
# CONFIG_CGROUPS is not set
CONFIG_NAMESPACES=y
# CONFIG_UTS_NS is not set
# CONFIG_TIME_NS is not set
# CONFIG_USER_NS is not set
# CONFIG_PID_NS is not set
# CONFIG_CHECKPOINT_RESTORE is not set
# CONFIG_SCHED_AUTOGROUP is not set
# CONFIG_SYSFS_DEPRECATED is not set
# CONFIG_RELAY is not set
# CONFIG_BLK_DEV_INITRD is not set
# CONFIG_BOOT_CONFIG is not set
# CONFIG_INITRAMFS_PRESERVE_MTIME is not set
CONFIG_CC_OPTIMIZE_FOR_PERFORMANCE=y
# CONFIG_CC_OPTIMIZE_FOR_SIZE is not set
CONFIG_LD_ORPHAN_WARN=y
CONFIG_SYSCTL=y
CONFIG_SYSCTL_EXCEPTION_TRACE=y
CONFIG_HAVE_PCSPKR_PLATFORM=y
# CONFIG_EXPERT is not set
CONFIG_MULTIUSER=y
CONFIG_SGETMASK_SYSCALL=y
CONFIG_SYSFS_SYSCALL=y
CONFIG_FHANDLE=y
CONFIG_POSIX_TIMERS=y
CONFIG_PRINTK=y
CONFIG_BUG=y
CONFIG_ELF_CORE=y
CONFIG_PCSPKR_PLATFORM=y
CONFIG_BASE_FULL=y
CONFIG_FUTEX=y
CONFIG_FUTEX_PI=y
CONFIG_EPOLL=y
CONFIG_SIGNALFD=y
CONFIG_TIMERFD=y
CONFIG_EVENTFD=y
CONFIG_SHMEM=y
CONFIG_AIO=y
CONFIG_IO_URING=y
CONFIG_ADVISE_SYSCALLS=y
CONFIG_MEMBARRIER=y
CONFIG_KALLSYMS=y
CONFIG_KALLSYMS_BASE_RELATIVE=y
CONFIG_ARCH_HAS_MEMBARRIER_SYNC_CORE=y
CONFIG_RSEQ=y
# CONFIG_EMBEDDED is not set
CONFIG_HAVE_PERF_EVENTS=y

#
# Kernel Performance Events And Counters
#
CONFIG_PERF_EVENTS=y
# end of Kernel Performance Events And Counters

# CONFIG_PROFILING is not set
# end of General setup

CONFIG_64BIT=y
CONFIG_X86_64=y
CONFIG_X86=y
CONFIG_INSTRUCTION_DECODER=y
CONFIG_OUTPUT_FORMAT="elf64-x86-64"
CONFIG_LOCKDEP_SUPPORT=y
CONFIG_STACKTRACE_SUPPORT=y
CONFIG_MMU=y
CONFIG_ARCH_MMAP_RND_BITS_MIN=28
CONFIG_ARCH_MMAP_RND_BITS_MAX=32
CONFIG_ARCH_MMAP_RND_COMPAT_BITS_MIN=8
CONFIG_ARCH_MMAP_RND_COMPAT_BITS_MAX=16
CONFIG_GENERIC_ISA_DMA=y
CONFIG_GENERIC_BUG=y
CONFIG_GENERIC_BUG_RELATIVE_POINTERS=y
CONFIG_ARCH_MAY_HAVE_PC_FDC=y
CONFIG_GENERIC_CALIBRATE_DELAY=y
CONFIG_ARCH_HAS_CPU_RELAX=y
CONFIG_ARCH_HIBERNATION_POSSIBLE=y
CONFIG_ARCH_NR_GPIO=1024
CONFIG_ARCH_SUSPEND_POSSIBLE=y
CONFIG_AUDIT_ARCH=y
CONFIG_ARCH_SUPPORTS_UPROBES=y
CONFIG_FIX_EARLYCON_MEM=y
CONFIG_PGTABLE_LEVELS=4
CONFIG_CC_HAS_SANE_STACKPROTECTOR=y

#
# Processor type and features
#
# CONFIG_SMP is not set
CONFIG_X86_FEATURE_NAMES=y
CONFIG_X86_MPPARSE=y
# CONFIG_GOLDFISH is not set
# CONFIG_X86_CPU_RESCTRL is not set
# CONFIG_X86_EXTENDED_PLATFORM is not set
# CONFIG_SCHED_OMIT_FRAME_POINTER is not set
# CONFIG_HYPERVISOR_GUEST is not set
# CONFIG_MK8 is not set
# CONFIG_MPSC is not set
# CONFIG_MCORE2 is not set
# CONFIG_MATOM is not set
CONFIG_GENERIC_CPU=y
CONFIG_X86_INTERNODE_CACHE_SHIFT=6
CONFIG_X86_L1_CACHE_SHIFT=6
CONFIG_X86_TSC=y
CONFIG_X86_CMPXCHG64=y
CONFIG_X86_CMOV=y
CONFIG_X86_MINIMUM_CPU_FAMILY=64
CONFIG_X86_DEBUGCTLMSR=y
CONFIG_IA32_FEAT_CTL=y
CONFIG_X86_VMX_FEATURE_NAMES=y
CONFIG_CPU_SUP_INTEL=y
CONFIG_CPU_SUP_AMD=y
CONFIG_CPU_SUP_HYGON=y
CONFIG_CPU_SUP_CENTAUR=y
CONFIG_CPU_SUP_ZHAOXIN=y
CONFIG_HPET_TIMER=y
CONFIG_DMI=y
CONFIG_NR_CPUS_RANGE_BEGIN=1
CONFIG_NR_CPUS_RANGE_END=1
CONFIG_NR_CPUS_DEFAULT=1
CONFIG_NR_CPUS=1
CONFIG_UP_LATE_INIT=y
CONFIG_X86_LOCAL_APIC=y
CONFIG_X86_IO_APIC=y
# CONFIG_X86_REROUTE_FOR_BROKEN_BOOT_IRQS is not set
# CONFIG_X86_MCE is not set

#
# Performance monitoring
#
# CONFIG_PERF_EVENTS_AMD_POWER is not set
# CONFIG_PERF_EVENTS_AMD_UNCORE is not set
# CONFIG_PERF_EVENTS_AMD_BRS is not set
# end of Performance monitoring

CONFIG_X86_16BIT=y
CONFIG_X86_ESPFIX64=y
CONFIG_X86_VSYSCALL_EMULATION=y
# CONFIG_X86_IOPL_IOPERM is not set
# CONFIG_MICROCODE is not set
# CONFIG_X86_MSR is not set
# CONFIG_X86_CPUID is not set
# CONFIG_X86_5LEVEL is not set
CONFIG_X86_DIRECT_GBPAGES=y
# CONFIG_AMD_MEM_ENCRYPT is not set
CONFIG_ARCH_SPARSEMEM_ENABLE=y
CONFIG_ARCH_SPARSEMEM_DEFAULT=y
CONFIG_ILLEGAL_POINTER_VALUE=0xdead000000000000
# CONFIG_X86_CHECK_BIOS_CORRUPTION is not set
CONFIG_MTRR=y
# CONFIG_MTRR_SANITIZER is not set
CONFIG_X86_PAT=y
CONFIG_ARCH_USES_PG_UNCACHED=y
CONFIG_X86_UMIP=y
CONFIG_CC_HAS_IBT=y
# CONFIG_X86_KERNEL_IBT is not set
# CONFIG_X86_INTEL_MEMORY_PROTECTION_KEYS is not set
CONFIG_X86_INTEL_TSX_MODE_OFF=y
# CONFIG_X86_INTEL_TSX_MODE_ON is not set
# CONFIG_X86_INTEL_TSX_MODE_AUTO is not set
# CONFIG_HZ_100 is not set
CONFIG_HZ_250=y
# CONFIG_HZ_300 is not set
# CONFIG_HZ_1000 is not set
CONFIG_HZ=250
# CONFIG_KEXEC is not set
# CONFIG_CRASH_DUMP is not set
CONFIG_PHYSICAL_START=0x1000000
# CONFIG_RELOCATABLE is not set
CONFIG_PHYSICAL_ALIGN=0x200000
CONFIG_LEGACY_VSYSCALL_XONLY=y
# CONFIG_LEGACY_VSYSCALL_NONE is not set
# CONFIG_CMDLINE_BOOL is not set
CONFIG_MODIFY_LDT_SYSCALL=y
# CONFIG_STRICT_SIGALTSTACK_SIZE is not set
CONFIG_HAVE_LIVEPATCH=y
# end of Processor type and features

CONFIG_CC_HAS_SLS=y
CONFIG_CC_HAS_RETURN_THUNK=y
# CONFIG_SPECULATION_MITIGATIONS is not set
CONFIG_ARCH_HAS_ADD_PAGES=y
CONFIG_ARCH_MHP_MEMMAP_ON_MEMORY_ENABLE=y

#
# Power management and ACPI options
#
# CONFIG_SUSPEND is not set
# CONFIG_PM is not set
CONFIG_ARCH_SUPPORTS_ACPI=y
# CONFIG_ACPI is not set

#
# CPU Frequency scaling
#
# CONFIG_CPU_FREQ is not set
# end of CPU Frequency scaling

#
# CPU Idle
#
# CONFIG_CPU_IDLE is not set
# end of CPU Idle
# end of Power management and ACPI options

#
# Bus options (PCI etc.)
#
CONFIG_ISA_DMA_API=y
# end of Bus options (PCI etc.)

#
# Binary Emulations
#
# CONFIG_IA32_EMULATION is not set
# CONFIG_X86_X32_ABI is not set
# end of Binary Emulations

CONFIG_HAVE_KVM=y
# CONFIG_VIRTUALIZATION is not set
CONFIG_AS_AVX512=y
CONFIG_AS_SHA1_NI=y
CONFIG_AS_SHA256_NI=y
CONFIG_AS_TPAUSE=y

#
# General architecture-dependent options
#
CONFIG_GENERIC_ENTRY=y
# CONFIG_JUMP_LABEL is not set
# CONFIG_STATIC_CALL_SELFTEST is not set
CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS=y
CONFIG_ARCH_USE_BUILTIN_BSWAP=y
CONFIG_HAVE_IOREMAP_PROT=y
CONFIG_HAVE_KPROBES=y
CONFIG_HAVE_KRETPROBES=y
CONFIG_HAVE_OPTPROBES=y
CONFIG_HAVE_KPROBES_ON_FTRACE=y
CONFIG_ARCH_CORRECT_STACKTRACE_ON_KRETPROBE=y
CONFIG_HAVE_FUNCTION_ERROR_INJECTION=y
CONFIG_HAVE_NMI=y
CONFIG_TRACE_IRQFLAGS_SUPPORT=y
CONFIG_TRACE_IRQFLAGS_NMI_SUPPORT=y
CONFIG_HAVE_ARCH_TRACEHOOK=y
CONFIG_HAVE_DMA_CONTIGUOUS=y
CONFIG_GENERIC_SMP_IDLE_THREAD=y
CONFIG_ARCH_HAS_FORTIFY_SOURCE=y
CONFIG_ARCH_HAS_SET_MEMORY=y
CONFIG_ARCH_HAS_SET_DIRECT_MAP=y
CONFIG_HAVE_ARCH_THREAD_STRUCT_WHITELIST=y
CONFIG_ARCH_WANTS_DYNAMIC_TASK_STRUCT=y
CONFIG_ARCH_WANTS_NO_INSTR=y
CONFIG_HAVE_ASM_MODVERSIONS=y
CONFIG_HAVE_REGS_AND_STACK_ACCESS_API=y
CONFIG_HAVE_RSEQ=y
CONFIG_HAVE_FUNCTION_ARG_ACCESS_API=y
CONFIG_HAVE_HW_BREAKPOINT=y
CONFIG_HAVE_MIXED_BREAKPOINTS_REGS=y
CONFIG_HAVE_USER_RETURN_NOTIFIER=y
CONFIG_HAVE_PERF_EVENTS_NMI=y
CONFIG_HAVE_HARDLOCKUP_DETECTOR_PERF=y
CONFIG_HAVE_PERF_REGS=y
CONFIG_HAVE_PERF_USER_STACK_DUMP=y
CONFIG_HAVE_ARCH_JUMP_LABEL=y
CONFIG_HAVE_ARCH_JUMP_LABEL_RELATIVE=y
CONFIG_MMU_GATHER_MERGE_VMAS=y
CONFIG_ARCH_HAVE_NMI_SAFE_CMPXCHG=y
CONFIG_HAVE_ALIGNED_STRUCT_PAGE=y
CONFIG_HAVE_CMPXCHG_LOCAL=y
CONFIG_HAVE_CMPXCHG_DOUBLE=y
CONFIG_HAVE_ARCH_SECCOMP=y
CONFIG_HAVE_ARCH_SECCOMP_FILTER=y
# CONFIG_SECCOMP is not set
CONFIG_HAVE_ARCH_STACKLEAK=y
CONFIG_HAVE_STACKPROTECTOR=y
# CONFIG_STACKPROTECTOR is not set
CONFIG_ARCH_SUPPORTS_LTO_CLANG=y
CONFIG_ARCH_SUPPORTS_LTO_CLANG_THIN=y
CONFIG_LTO_NONE=y
CONFIG_HAVE_ARCH_WITHIN_STACK_FRAMES=y
CONFIG_HAVE_CONTEXT_TRACKING_USER=y
CONFIG_HAVE_CONTEXT_TRACKING_USER_OFFSTACK=y
CONFIG_HAVE_VIRT_CPU_ACCOUNTING_GEN=y
CONFIG_HAVE_IRQ_TIME_ACCOUNTING=y
CONFIG_HAVE_MOVE_PUD=y
CONFIG_HAVE_MOVE_PMD=y
CONFIG_HAVE_ARCH_TRANSPARENT_HUGEPAGE=y
CONFIG_HAVE_ARCH_TRANSPARENT_HUGEPAGE_PUD=y
CONFIG_HAVE_ARCH_HUGE_VMAP=y
CONFIG_HAVE_ARCH_HUGE_VMALLOC=y
CONFIG_ARCH_WANT_HUGE_PMD_SHARE=y
CONFIG_HAVE_ARCH_SOFT_DIRTY=y
CONFIG_HAVE_MOD_ARCH_SPECIFIC=y
CONFIG_MODULES_USE_ELF_RELA=y
CONFIG_HAVE_IRQ_EXIT_ON_IRQ_STACK=y
CONFIG_HAVE_SOFTIRQ_ON_OWN_STACK=y
CONFIG_ARCH_HAS_ELF_RANDOMIZE=y
CONFIG_HAVE_ARCH_MMAP_RND_BITS=y
CONFIG_HAVE_EXIT_THREAD=y
CONFIG_ARCH_MMAP_RND_BITS=28
CONFIG_PAGE_SIZE_LESS_THAN_64KB=y
CONFIG_PAGE_SIZE_LESS_THAN_256KB=y
CONFIG_HAVE_OBJTOOL=y
CONFIG_HAVE_JUMP_LABEL_HACK=y
CONFIG_HAVE_NOINSTR_HACK=y
CONFIG_HAVE_NOINSTR_VALIDATION=y
CONFIG_HAVE_UACCESS_VALIDATION=y
CONFIG_HAVE_STACK_VALIDATION=y
CONFIG_HAVE_RELIABLE_STACKTRACE=y
# CONFIG_COMPAT_32BIT_TIME is not set
CONFIG_HAVE_ARCH_VMAP_STACK=y
# CONFIG_VMAP_STACK is not set
CONFIG_HAVE_ARCH_RANDOMIZE_KSTACK_OFFSET=y
CONFIG_RANDOMIZE_KSTACK_OFFSET=y
# CONFIG_RANDOMIZE_KSTACK_OFFSET_DEFAULT is not set
CONFIG_ARCH_HAS_STRICT_KERNEL_RWX=y
CONFIG_STRICT_KERNEL_RWX=y
CONFIG_ARCH_HAS_STRICT_MODULE_RWX=y
CONFIG_HAVE_ARCH_PREL32_RELOCATIONS=y
CONFIG_ARCH_HAS_MEM_ENCRYPT=y
CONFIG_HAVE_STATIC_CALL=y
CONFIG_HAVE_STATIC_CALL_INLINE=y
CONFIG_HAVE_PREEMPT_DYNAMIC=y
CONFIG_HAVE_PREEMPT_DYNAMIC_CALL=y
CONFIG_ARCH_WANT_LD_ORPHAN_WARN=y
CONFIG_ARCH_SUPPORTS_DEBUG_PAGEALLOC=y
CONFIG_ARCH_SUPPORTS_PAGE_TABLE_CHECK=y
CONFIG_ARCH_HAS_ELFCORE_COMPAT=y
CONFIG_ARCH_HAS_PARANOID_L1D_FLUSH=y
CONFIG_DYNAMIC_SIGFRAME=y

#
# GCOV-based kernel profiling
#
CONFIG_ARCH_HAS_GCOV_PROFILE_ALL=y
# end of GCOV-based kernel profiling

CONFIG_HAVE_GCC_PLUGINS=y
# CONFIG_GCC_PLUGINS is not set
# end of General architecture-dependent options

CONFIG_RT_MUTEXES=y
CONFIG_BASE_SMALL=0
# CONFIG_MODULES is not set
CONFIG_BLOCK=y
# CONFIG_BLOCK_LEGACY_AUTOLOAD is not set
# CONFIG_BLK_DEV_BSGLIB is not set
# CONFIG_BLK_DEV_INTEGRITY is not set
# CONFIG_BLK_DEV_ZONED is not set
# CONFIG_BLK_WBT is not set
# CONFIG_BLK_SED_OPAL is not set
# CONFIG_BLK_INLINE_ENCRYPTION is not set

#
# Partition Types
#
# CONFIG_PARTITION_ADVANCED is not set
CONFIG_MSDOS_PARTITION=y
CONFIG_EFI_PARTITION=y
# end of Partition Types

#
# IO Schedulers
#
# CONFIG_MQ_IOSCHED_DEADLINE is not set
# CONFIG_MQ_IOSCHED_KYBER is not set
# CONFIG_IOSCHED_BFQ is not set
# end of IO Schedulers

CONFIG_INLINE_SPIN_UNLOCK_IRQ=y
CONFIG_INLINE_READ_UNLOCK=y
CONFIG_INLINE_READ_UNLOCK_IRQ=y
CONFIG_INLINE_WRITE_UNLOCK=y
CONFIG_INLINE_WRITE_UNLOCK_IRQ=y
CONFIG_ARCH_SUPPORTS_ATOMIC_RMW=y
CONFIG_ARCH_USE_QUEUED_SPINLOCKS=y
CONFIG_ARCH_USE_QUEUED_RWLOCKS=y
CONFIG_ARCH_HAS_NON_OVERLAPPING_ADDRESS_SPACE=y
CONFIG_ARCH_HAS_SYNC_CORE_BEFORE_USERMODE=y
CONFIG_ARCH_HAS_SYSCALL_WRAPPER=y

#
# Executable file formats
#
# CONFIG_BINFMT_ELF is not set
# CONFIG_BINFMT_SCRIPT is not set
# CONFIG_BINFMT_MISC is not set
CONFIG_COREDUMP=y
# end of Executable file formats

#
# Memory Management options
#
# CONFIG_SWAP is not set

#
# SLAB allocator options
#
# CONFIG_SLAB is not set
CONFIG_SLUB=y
# CONFIG_SLAB_MERGE_DEFAULT is not set
# CONFIG_SLAB_FREELIST_RANDOM is not set
# CONFIG_SLAB_FREELIST_HARDENED is not set
# CONFIG_SLUB_STATS is not set
# end of SLAB allocator options

# CONFIG_SHUFFLE_PAGE_ALLOCATOR is not set
# CONFIG_COMPAT_BRK is not set
CONFIG_SPARSEMEM=y
CONFIG_SPARSEMEM_EXTREME=y
CONFIG_SPARSEMEM_VMEMMAP_ENABLE=y
# CONFIG_SPARSEMEM_VMEMMAP is not set
CONFIG_HAVE_FAST_GUP=y
CONFIG_EXCLUSIVE_SYSTEM_RAM=y
CONFIG_ARCH_ENABLE_MEMORY_HOTPLUG=y
# CONFIG_MEMORY_HOTPLUG is not set
CONFIG_SPLIT_PTLOCK_CPUS=4
CONFIG_ARCH_ENABLE_SPLIT_PMD_PTLOCK=y
# CONFIG_COMPACTION is not set
# CONFIG_PAGE_REPORTING is not set
CONFIG_PHYS_ADDR_T_64BIT=y
# CONFIG_KSM is not set
CONFIG_DEFAULT_MMAP_MIN_ADDR=4096
CONFIG_ARCH_WANT_GENERAL_HUGETLB=y
CONFIG_ARCH_WANTS_THP_SWAP=y
# CONFIG_TRANSPARENT_HUGEPAGE is not set
CONFIG_NEED_PER_CPU_KM=y
CONFIG_NEED_PER_CPU_EMBED_FIRST_CHUNK=y
CONFIG_NEED_PER_CPU_PAGE_FIRST_CHUNK=y
CONFIG_HAVE_SETUP_PER_CPU_AREA=y
# CONFIG_CMA is not set
CONFIG_GENERIC_EARLY_IOREMAP=y
# CONFIG_IDLE_PAGE_TRACKING is not set
CONFIG_ARCH_HAS_CACHE_LINE_SIZE=y
CONFIG_ARCH_HAS_CURRENT_STACK_POINTER=y
CONFIG_ARCH_HAS_PTE_DEVMAP=y
CONFIG_ZONE_DMA=y
CONFIG_ZONE_DMA32=y
CONFIG_VM_EVENT_COUNTERS=y
# CONFIG_PERCPU_STATS is not set

#
# GUP_TEST needs to have DEBUG_FS enabled
#
CONFIG_ARCH_HAS_PTE_SPECIAL=y
CONFIG_SECRETMEM=y
# CONFIG_ANON_VMA_NAME is not set
# CONFIG_USERFAULTFD is not set

#
# Data Access Monitoring
#
# CONFIG_DAMON is not set
# end of Data Access Monitoring
# end of Memory Management options

# CONFIG_NET is not set

#
# Device Drivers
#
CONFIG_HAVE_EISA=y
# CONFIG_EISA is not set
CONFIG_HAVE_PCI=y
# CONFIG_PCI is not set
# CONFIG_PCCARD is not set

#
# Generic Driver Options
#
# CONFIG_UEVENT_HELPER is not set
# CONFIG_DEVTMPFS is not set
# CONFIG_STANDALONE is not set
# CONFIG_PREVENT_FIRMWARE_BUILD is not set

#
# Firmware loader
#
CONFIG_FW_LOADER=y
CONFIG_EXTRA_FIRMWARE=""
# CONFIG_FW_LOADER_USER_HELPER is not set
# CONFIG_FW_LOADER_COMPRESS is not set
# CONFIG_FW_UPLOAD is not set
# end of Firmware loader

CONFIG_ALLOW_DEV_COREDUMP=y
CONFIG_GENERIC_CPU_AUTOPROBE=y
CONFIG_GENERIC_CPU_VULNERABILITIES=y
# end of Generic Driver Options

#
# Bus devices
#
# CONFIG_ARM_INTEGRATOR_LM is not set
# CONFIG_BT1_APB is not set
# CONFIG_BT1_AXI is not set
# CONFIG_HISILICON_LPC is not set
# CONFIG_INTEL_IXP4XX_EB is not set
# CONFIG_QCOM_EBI2 is not set
# CONFIG_MHI_BUS is not set
# CONFIG_MHI_BUS_EP is not set
# end of Bus devices

#
# Firmware Drivers
#

#
# ARM System Control and Management Interface Protocol
#
# CONFIG_ARM_SCMI_PROTOCOL is not set
# end of ARM System Control and Management Interface Protocol

# CONFIG_EDD is not set
CONFIG_FIRMWARE_MEMMAP=y
# CONFIG_DMIID is not set
# CONFIG_DMI_SYSFS is not set
CONFIG_DMI_SCAN_MACHINE_NON_EFI_FALLBACK=y
# CONFIG_FW_CFG_SYSFS is not set
# CONFIG_SYSFB_SIMPLEFB is not set
# CONFIG_BCM47XX_NVRAM is not set
# CONFIG_GOOGLE_FIRMWARE is not set

#
# Tegra firmware driver
#
# end of Tegra firmware driver
# end of Firmware Drivers

# CONFIG_GNSS is not set
# CONFIG_MTD is not set
# CONFIG_OF is not set
CONFIG_ARCH_MIGHT_HAVE_PC_PARPORT=y
# CONFIG_PARPORT is not set
# CONFIG_BLK_DEV is not set

#
# NVME Support
#
# CONFIG_NVME_FC is not set
# end of NVME Support

#
# Misc devices
#
# CONFIG_DUMMY_IRQ is not set
# CONFIG_ATMEL_SSC is not set
# CONFIG_ENCLOSURE_SERVICES is not set
# CONFIG_QCOM_COINCELL is not set
# CONFIG_SRAM is not set
# CONFIG_XILINX_SDFEC is not set
# CONFIG_C2PORT is not set

#
# EEPROM support
#
# CONFIG_EEPROM_93CX6 is not set
# end of EEPROM support

#
# Texas Instruments shared transport line discipline
#
# end of Texas Instruments shared transport line discipline

#
# Altera FPGA firmware download module (requires I2C)
#
# CONFIG_ECHO is not set
# CONFIG_PVPANIC is not set
# end of Misc devices

#
# SCSI device support
#
CONFIG_SCSI_MOD=y
# CONFIG_RAID_ATTRS is not set
# CONFIG_SCSI is not set
# end of SCSI device support

# CONFIG_ATA is not set
# CONFIG_MD is not set
# CONFIG_TARGET_CORE is not set

#
# IEEE 1394 (FireWire) support
#
# CONFIG_FIREWIRE is not set
# end of IEEE 1394 (FireWire) support

# CONFIG_MACINTOSH_DRIVERS is not set

#
# Input device support
#
CONFIG_INPUT=y
# CONFIG_INPUT_FF_MEMLESS is not set
# CONFIG_INPUT_SPARSEKMAP is not set
# CONFIG_INPUT_MATRIXKMAP is not set

#
# Userland interfaces
#
# CONFIG_INPUT_MOUSEDEV is not set
# CONFIG_INPUT_JOYDEV is not set
# CONFIG_INPUT_EVDEV is not set
# CONFIG_INPUT_EVBUG is not set

#
# Input Device Drivers
#
# CONFIG_INPUT_KEYBOARD is not set
# CONFIG_INPUT_MOUSE is not set
# CONFIG_INPUT_JOYSTICK is not set
# CONFIG_INPUT_TABLET is not set
# CONFIG_INPUT_TOUCHSCREEN is not set
# CONFIG_INPUT_MISC is not set
# CONFIG_RMI4_CORE is not set

#
# Hardware I/O ports
#
# CONFIG_SERIO is not set
CONFIG_ARCH_MIGHT_HAVE_PC_SERIO=y
# CONFIG_GAMEPORT is not set
# end of Hardware I/O ports
# end of Input device support

#
# Character devices
#
CONFIG_TTY=y
CONFIG_VT=y
CONFIG_CONSOLE_TRANSLATIONS=y
CONFIG_VT_CONSOLE=y
CONFIG_HW_CONSOLE=y
# CONFIG_VT_HW_CONSOLE_BINDING is not set
CONFIG_UNIX98_PTYS=y
# CONFIG_LEGACY_PTYS is not set
# CONFIG_LDISC_AUTOLOAD is not set

#
# Serial drivers
#
# CONFIG_SERIAL_8250 is not set

#
# Non-8250 serial port support
#
# CONFIG_SERIAL_AMBA_PL010 is not set
# CONFIG_SERIAL_ATMEL is not set
# CONFIG_SERIAL_MESON is not set
# CONFIG_SERIAL_CLPS711X is not set
# CONFIG_SERIAL_SAMSUNG is not set
# CONFIG_SERIAL_TEGRA is not set
# CONFIG_SERIAL_IMX is not set
# CONFIG_SERIAL_UARTLITE is not set
# CONFIG_SERIAL_SH_SCI is not set
# CONFIG_SERIAL_MSM is not set
# CONFIG_SERIAL_VT8500 is not set
# CONFIG_SERIAL_OMAP is not set
# CONFIG_SERIAL_LANTIQ is not set
# CONFIG_SERIAL_SCCNXP is not set
# CONFIG_SERIAL_TIMBERDALE is not set
# CONFIG_SERIAL_BCM63XX is not set
# CONFIG_SERIAL_ALTERA_JTAGUART is not set
# CONFIG_SERIAL_ALTERA_UART is not set
# CONFIG_SERIAL_MXS_AUART is not set
# CONFIG_SERIAL_MPS2_UART is not set
# CONFIG_SERIAL_ARC is not set
# CONFIG_SERIAL_FSL_LPUART is not set
# CONFIG_SERIAL_FSL_LINFLEXUART is not set
# CONFIG_SERIAL_ST_ASC is not set
# CONFIG_SERIAL_STM32 is not set
# CONFIG_SERIAL_OWL is not set
# CONFIG_SERIAL_RDA is not set
# CONFIG_SERIAL_LITEUART is not set
# CONFIG_SERIAL_SUNPLUS is not set
# end of Serial drivers

# CONFIG_SERIAL_NONSTANDARD is not set
# CONFIG_NULL_TTY is not set
# CONFIG_SERIAL_DEV_BUS is not set
# CONFIG_VIRTIO_CONSOLE is not set
# CONFIG_IPMI_HANDLER is not set
# CONFIG_ASPEED_KCS_IPMI_BMC is not set
# CONFIG_NPCM7XX_KCS_IPMI_BMC is not set
# CONFIG_HW_RANDOM is not set
# CONFIG_MWAVE is not set
# CONFIG_DEVMEM is not set
# CONFIG_NVRAM is not set
# CONFIG_HANGCHECK_TIMER is not set
# CONFIG_TCG_TPM is not set
# CONFIG_TELCLOCK is not set
# CONFIG_RANDOM_TRUST_CPU is not set
# CONFIG_RANDOM_TRUST_BOOTLOADER is not set
# end of Character devices

#
# I2C support
#
# CONFIG_I2C is not set
# end of I2C support

# CONFIG_I3C is not set
# CONFIG_SPI is not set
# CONFIG_SPMI is not set
# CONFIG_HSI is not set
# CONFIG_PPS is not set

#
# PTP clock support
#
CONFIG_PTP_1588_CLOCK_OPTIONAL=y

#
# Enable PHYLIB and NETWORK_PHY_TIMESTAMPING to see the additional clocks.
#
# end of PTP clock support

# CONFIG_PINCTRL is not set
# CONFIG_GPIOLIB is not set
# CONFIG_W1 is not set
# CONFIG_POWER_RESET is not set
# CONFIG_POWER_SUPPLY is not set
# CONFIG_HWMON is not set
# CONFIG_THERMAL is not set
# CONFIG_WATCHDOG is not set
CONFIG_SSB_POSSIBLE=y
# CONFIG_SSB is not set
CONFIG_BCMA_POSSIBLE=y
# CONFIG_BCMA is not set

#
# Multifunction device drivers
#
# CONFIG_MFD_SUN4I_GPADC is not set
# CONFIG_MFD_AT91_USART is not set
# CONFIG_MFD_MADERA is not set
# CONFIG_MFD_EXYNOS_LPASS is not set
# CONFIG_MFD_MXS_LRADC is not set
# CONFIG_MFD_MX25_TSADC is not set
# CONFIG_HTC_PASIC3 is not set
# CONFIG_MFD_KEMPLD is not set
# CONFIG_MFD_MT6397 is not set
# CONFIG_MFD_PM8XXX is not set
# CONFIG_MFD_SM501 is not set
# CONFIG_ABX500_CORE is not set
# CONFIG_MFD_SUN6I_PRCM is not set
# CONFIG_MFD_SYSCON is not set
# CONFIG_MFD_TI_AM335X_TSCADC is not set
# CONFIG_MFD_TQMX86 is not set
# CONFIG_MFD_STM32_LPTIMER is not set
# CONFIG_MFD_STM32_TIMERS is not set
# end of Multifunction device drivers

# CONFIG_REGULATOR is not set
# CONFIG_RC_CORE is not set

#
# CEC support
#
# CONFIG_MEDIA_CEC_SUPPORT is not set
# end of CEC support

# CONFIG_MEDIA_SUPPORT is not set

#
# Graphics support
#
# CONFIG_IMX_IPUV3_CORE is not set
# CONFIG_DRM is not set

#
# ARM devices
#
# end of ARM devices

#
# Frame buffer Devices
#
# CONFIG_FB is not set
# CONFIG_MMP_DISP is not set
# end of Frame buffer Devices

#
# Backlight & LCD device support
#
# CONFIG_LCD_CLASS_DEVICE is not set
# CONFIG_BACKLIGHT_CLASS_DEVICE is not set
# end of Backlight & LCD device support

#
# Console display driver support
#
CONFIG_VGA_CONSOLE=y
CONFIG_DUMMY_CONSOLE=y
CONFIG_DUMMY_CONSOLE_COLUMNS=80
CONFIG_DUMMY_CONSOLE_ROWS=25
# end of Console display driver support
# end of Graphics support

# CONFIG_SOUND is not set

#
# HID support
#
# CONFIG_HID is not set
# end of HID support

CONFIG_USB_OHCI_LITTLE_ENDIAN=y
# CONFIG_USB_SUPPORT is not set
# CONFIG_MMC is not set
# CONFIG_MEMSTICK is not set
# CONFIG_NEW_LEDS is not set
# CONFIG_ACCESSIBILITY is not set
CONFIG_EDAC_ATOMIC_SCRUB=y
CONFIG_EDAC_SUPPORT=y
CONFIG_RTC_LIB=y
CONFIG_RTC_MC146818_LIB=y
# CONFIG_RTC_CLASS is not set
# CONFIG_DMADEVICES is not set

#
# DMABUF options
#
# CONFIG_SYNC_FILE is not set
# CONFIG_DMABUF_HEAPS is not set
# end of DMABUF options

# CONFIG_AUXDISPLAY is not set
# CONFIG_UIO is not set
# CONFIG_VFIO is not set
# CONFIG_VIRT_DRIVERS is not set
# CONFIG_VIRTIO_MENU is not set
# CONFIG_VHOST_MENU is not set

#
# Microsoft Hyper-V guest support
#
# end of Microsoft Hyper-V guest support

# CONFIG_GREYBUS is not set
# CONFIG_COMEDI is not set
# CONFIG_STAGING is not set
# CONFIG_CHROME_PLATFORMS is not set
# CONFIG_MELLANOX_PLATFORM is not set
# CONFIG_OLPC_XO175 is not set
# CONFIG_SURFACE_PLATFORMS is not set
# CONFIG_X86_PLATFORM_DEVICES is not set
# CONFIG_COMMON_CLK is not set
# CONFIG_HWSPINLOCK is not set

#
# Clock Source drivers
#
CONFIG_CLKEVT_I8253=y
CONFIG_I8253_LOCK=y
CONFIG_CLKBLD_I8253=y
# CONFIG_BCM2835_TIMER is not set
# CONFIG_BCM_KONA_TIMER is not set
# CONFIG_DAVINCI_TIMER is not set
# CONFIG_DIGICOLOR_TIMER is not set
# CONFIG_OMAP_DM_TIMER is not set
# CONFIG_DW_APB_TIMER is not set
# CONFIG_FTTMR010_TIMER is not set
# CONFIG_IXP4XX_TIMER is not set
# CONFIG_MESON6_TIMER is not set
# CONFIG_OWL_TIMER is not set
# CONFIG_RDA_TIMER is not set
# CONFIG_SUN4I_TIMER is not set
# CONFIG_TEGRA_TIMER is not set
# CONFIG_VT8500_TIMER is not set
# CONFIG_NPCM7XX_TIMER is not set
# CONFIG_ASM9260_TIMER is not set
# CONFIG_CLKSRC_DBX500_PRCMU is not set
# CONFIG_CLPS711X_TIMER is not set
# CONFIG_MXS_TIMER is not set
# CONFIG_NSPIRE_TIMER is not set
# CONFIG_INTEGRATOR_AP_TIMER is not set
# CONFIG_CLKSRC_PISTACHIO is not set
# CONFIG_CLKSRC_STM32_LP is not set
# CONFIG_ARMV7M_SYSTICK is not set
# CONFIG_ATMEL_PIT is not set
# CONFIG_ATMEL_ST is not set
# CONFIG_CLKSRC_SAMSUNG_PWM is not set
# CONFIG_FSL_FTM_TIMER is not set
# CONFIG_OXNAS_RPS_TIMER is not set
# CONFIG_MTK_TIMER is not set
# CONFIG_SH_TIMER_CMT is not set
# CONFIG_SH_TIMER_MTU2 is not set
# CONFIG_RENESAS_OSTM is not set
# CONFIG_SH_TIMER_TMU is not set
# CONFIG_EM_TIMER_STI is not set
# CONFIG_CLKSRC_PXA is not set
# CONFIG_TIMER_IMX_SYS_CTR is not set
# CONFIG_CLKSRC_ST_LPC is not set
# CONFIG_GXP_TIMER is not set
# CONFIG_MSC313E_TIMER is not set
# CONFIG_MICROCHIP_PIT64B is not set
# end of Clock Source drivers

# CONFIG_MAILBOX is not set
# CONFIG_IOMMU_SUPPORT is not set

#
# Remoteproc drivers
#
# CONFIG_REMOTEPROC is not set
# end of Remoteproc drivers

#
# Rpmsg drivers
#
# CONFIG_RPMSG_VIRTIO is not set
# end of Rpmsg drivers

#
# SOC (System On Chip) specific Drivers
#

#
# Amlogic SoC drivers
#
# CONFIG_MESON_CANVAS is not set
# CONFIG_MESON_CLK_MEASURE is not set
# CONFIG_MESON_GX_SOCINFO is not set
# CONFIG_MESON_MX_SOCINFO is not set
# end of Amlogic SoC drivers

#
# Apple SoC drivers
#
# CONFIG_APPLE_SART is not set
# end of Apple SoC drivers

#
# ASPEED SoC drivers
#
# CONFIG_ASPEED_LPC_CTRL is not set
# CONFIG_ASPEED_LPC_SNOOP is not set
# CONFIG_ASPEED_UART_ROUTING is not set
# CONFIG_ASPEED_P2A_CTRL is not set
# CONFIG_ASPEED_SOCINFO is not set
# end of ASPEED SoC drivers

# CONFIG_AT91_SOC_ID is not set
# CONFIG_AT91_SOC_SFR is not set

#
# Broadcom SoC drivers
#
# CONFIG_SOC_BCM63XX is not set
# CONFIG_SOC_BRCMSTB is not set
# end of Broadcom SoC drivers

#
# NXP/Freescale QorIQ SoC drivers
#
# end of NXP/Freescale QorIQ SoC drivers

#
# fujitsu SoC drivers
#
# end of fujitsu SoC drivers

#
# i.MX SoC drivers
#
# CONFIG_SOC_IMX8M is not set
# end of i.MX SoC drivers

#
# IXP4xx SoC drivers
#
# CONFIG_IXP4XX_QMGR is not set
# CONFIG_IXP4XX_NPE is not set
# end of IXP4xx SoC drivers

#
# Enable LiteX SoC Builder specific drivers
#
# CONFIG_LITEX_SOC_CONTROLLER is not set
# end of Enable LiteX SoC Builder specific drivers

#
# MediaTek SoC drivers
#
# CONFIG_MTK_CMDQ is not set
# CONFIG_MTK_DEVAPC is not set
# CONFIG_MTK_INFRACFG is not set
# CONFIG_MTK_SCPSYS is not set
# CONFIG_MTK_MMSYS is not set
# end of MediaTek SoC drivers

#
# Qualcomm SoC drivers
#
# CONFIG_QCOM_GENI_SE is not set
# CONFIG_QCOM_GSBI is not set
# CONFIG_QCOM_LLCC is not set
# CONFIG_QCOM_RPMH is not set
# CONFIG_QCOM_SPM is not set
# CONFIG_QCOM_ICC_BWMON is not set
# end of Qualcomm SoC drivers

# CONFIG_SOC_RENESAS is not set
# CONFIG_ROCKCHIP_GRF is not set
# CONFIG_SOC_SAMSUNG is not set
# CONFIG_SOC_TI is not set
# CONFIG_UX500_SOC_ID is not set

#
# Xilinx SoC drivers
#
# end of Xilinx SoC drivers
# end of SOC (System On Chip) specific Drivers

# CONFIG_PM_DEVFREQ is not set
# CONFIG_EXTCON is not set
# CONFIG_MEMORY is not set
# CONFIG_IIO is not set
# CONFIG_PWM is not set

#
# IRQ chip support
#
# CONFIG_AL_FIC is not set
# CONFIG_RENESAS_INTC_IRQPIN is not set
# CONFIG_RENESAS_IRQC is not set
# CONFIG_RENESAS_RZA1_IRQC is not set
# CONFIG_RENESAS_RZG2L_IRQC is not set
# CONFIG_SL28CPLD_INTC is not set
# CONFIG_TS4800_IRQ is not set
# CONFIG_INGENIC_TCU_IRQ is not set
# CONFIG_IRQ_UNIPHIER_AIDET is not set
# CONFIG_MESON_IRQ_GPIO is not set
# CONFIG_IMX_IRQSTEER is not set
# CONFIG_IMX_INTMUX is not set
# CONFIG_EXYNOS_IRQ_COMBINER is not set
# CONFIG_MST_IRQ is not set
# CONFIG_MCHP_EIC is not set
# CONFIG_SUNPLUS_SP7021_INTC is not set
# end of IRQ chip support

# CONFIG_IPACK_BUS is not set
# CONFIG_RESET_CONTROLLER is not set

#
# PHY Subsystem
#
# CONFIG_GENERIC_PHY is not set
# CONFIG_PHY_PISTACHIO_USB is not set
# CONFIG_PHY_CAN_TRANSCEIVER is not set

#
# PHY drivers for Broadcom platforms
#
# CONFIG_PHY_BCM63XX_USBH is not set
# CONFIG_BCM_KONA_USB2_PHY is not set
# end of PHY drivers for Broadcom platforms

# CONFIG_PHY_HI6220_USB is not set
# CONFIG_PHY_HI3660_USB is not set
# CONFIG_PHY_HI3670_USB is not set
# CONFIG_PHY_HI3670_PCIE is not set
# CONFIG_PHY_HISTB_COMBPHY is not set
# CONFIG_PHY_HISI_INNO_USB2 is not set
# CONFIG_PHY_PXA_28NM_HSIC is not set
# CONFIG_PHY_PXA_28NM_USB2 is not set
# CONFIG_PHY_PXA_USB is not set
# CONFIG_PHY_MMP3_USB is not set
# CONFIG_PHY_MMP3_HSIC is not set
# CONFIG_PHY_MT7621_PCI is not set
# CONFIG_PHY_RALINK_USB is not set
# CONFIG_PHY_RCAR_GEN3_USB3 is not set
# CONFIG_PHY_ROCKCHIP_DPHY_RX0 is not set
# CONFIG_PHY_ROCKCHIP_PCIE is not set
# CONFIG_PHY_EXYNOS_MIPI_VIDEO is not set
# CONFIG_PHY_SAMSUNG_USB2 is not set
# CONFIG_PHY_ST_SPEAR1310_MIPHY is not set
# CONFIG_PHY_ST_SPEAR1340_MIPHY is not set
# CONFIG_PHY_TEGRA194_P2U is not set
# CONFIG_PHY_DA8XX_USB is not set
# CONFIG_OMAP_CONTROL_PHY is not set
# CONFIG_TI_PIPE3 is not set
# CONFIG_PHY_INTEL_KEEMBAY_EMMC is not set
# CONFIG_PHY_INTEL_KEEMBAY_USB is not set
# CONFIG_PHY_INTEL_LGM_EMMC is not set
# CONFIG_PHY_XILINX_ZYNQMP is not set
# end of PHY Subsystem

# CONFIG_POWERCAP is not set
# CONFIG_MCB is not set

#
# Performance monitor support
#
# CONFIG_ARM_CCN is not set
# CONFIG_ARM_CMN is not set
# CONFIG_FSL_IMX8_DDR_PMU is not set
# CONFIG_XGENE_PMU is not set
# CONFIG_ARM_DMC620_PMU is not set
# CONFIG_MARVELL_CN10K_TAD_PMU is not set
# CONFIG_MARVELL_CN10K_DDR_PMU is not set
# end of Performance monitor support

# CONFIG_RAS is not set

#
# Android
#
# CONFIG_ANDROID_BINDER_IPC is not set
# end of Android

# CONFIG_DAX is not set
# CONFIG_NVMEM is not set

#
# HW tracing support
#
# CONFIG_STM is not set
# CONFIG_INTEL_TH is not set
# end of HW tracing support

# CONFIG_FPGA is not set
# CONFIG_TEE is not set
# CONFIG_SIOX is not set
# CONFIG_SLIMBUS is not set
# CONFIG_INTERCONNECT is not set
# CONFIG_COUNTER is not set
# CONFIG_PECI is not set
# CONFIG_HTE is not set
# end of Device Drivers

#
# File systems
#
CONFIG_DCACHE_WORD_ACCESS=y
# CONFIG_VALIDATE_FS_PARSER is not set
# CONFIG_EXT2_FS is not set
# CONFIG_EXT3_FS is not set
# CONFIG_EXT4_FS is not set
# CONFIG_REISERFS_FS is not set
# CONFIG_JFS_FS is not set
# CONFIG_XFS_FS is not set
# CONFIG_GFS2_FS is not set
# CONFIG_BTRFS_FS is not set
# CONFIG_NILFS2_FS is not set
# CONFIG_F2FS_FS is not set
CONFIG_EXPORTFS=y
# CONFIG_EXPORTFS_BLOCK_OPS is not set
CONFIG_FILE_LOCKING=y
# CONFIG_FS_ENCRYPTION is not set
# CONFIG_FS_VERITY is not set
# CONFIG_DNOTIFY is not set
# CONFIG_INOTIFY_USER is not set
# CONFIG_FANOTIFY is not set
# CONFIG_QUOTA is not set
# CONFIG_AUTOFS4_FS is not set
# CONFIG_AUTOFS_FS is not set
# CONFIG_FUSE_FS is not set
# CONFIG_OVERLAY_FS is not set

#
# Caches
#
# CONFIG_FSCACHE is not set
# end of Caches

#
# CD-ROM/DVD Filesystems
#
# CONFIG_ISO9660_FS is not set
# CONFIG_UDF_FS is not set
# end of CD-ROM/DVD Filesystems

#
# DOS/FAT/EXFAT/NT Filesystems
#
# CONFIG_MSDOS_FS is not set
# CONFIG_VFAT_FS is not set
# CONFIG_EXFAT_FS is not set
# CONFIG_NTFS_FS is not set
# CONFIG_NTFS3_FS is not set
# end of DOS/FAT/EXFAT/NT Filesystems

#
# Pseudo filesystems
#
CONFIG_PROC_FS=y
# CONFIG_PROC_KCORE is not set
CONFIG_PROC_SYSCTL=y
CONFIG_PROC_PAGE_MONITOR=y
# CONFIG_PROC_CHILDREN is not set
CONFIG_PROC_PID_ARCH_STATUS=y
CONFIG_KERNFS=y
CONFIG_SYSFS=y
# CONFIG_TMPFS is not set
# CONFIG_HUGETLBFS is not set
CONFIG_ARCH_WANT_HUGETLB_PAGE_OPTIMIZE_VMEMMAP=y
CONFIG_ARCH_HAS_GIGANTIC_PAGE=y
# CONFIG_CONFIGFS_FS is not set
# end of Pseudo filesystems

# CONFIG_MISC_FILESYSTEMS is not set
# CONFIG_NLS is not set
# CONFIG_UNICODE is not set
CONFIG_IO_WQ=y
# end of File systems

#
# Security options
#
# CONFIG_KEYS is not set
# CONFIG_SECURITY_DMESG_RESTRICT is not set
# CONFIG_SECURITY is not set
# CONFIG_SECURITYFS is not set
CONFIG_HAVE_HARDENED_USERCOPY_ALLOCATOR=y
# CONFIG_HARDENED_USERCOPY is not set
# CONFIG_FORTIFY_SOURCE is not set
# CONFIG_STATIC_USERMODEHELPER is not set
CONFIG_DEFAULT_SECURITY_DAC=y
CONFIG_LSM="landlock,lockdown,yama,loadpin,safesetid,integrity,bpf"

#
# Kernel hardening options
#

#
# Memory initialization
#
CONFIG_INIT_STACK_NONE=y
# CONFIG_INIT_ON_ALLOC_DEFAULT_ON is not set
# CONFIG_INIT_ON_FREE_DEFAULT_ON is not set
CONFIG_CC_HAS_ZERO_CALL_USED_REGS=y
# CONFIG_ZERO_CALL_USED_REGS is not set
# end of Memory initialization

CONFIG_RANDSTRUCT_NONE=y
# end of Kernel hardening options
# end of Security options

# CONFIG_CRYPTO is not set

#
# Library routines
#
# CONFIG_PACKING is not set
CONFIG_BITREVERSE=y
CONFIG_GENERIC_STRNCPY_FROM_USER=y
CONFIG_GENERIC_STRNLEN_USER=y
# CONFIG_CORDIC is not set
# CONFIG_PRIME_NUMBERS is not set
CONFIG_GENERIC_PCI_IOMAP=y
CONFIG_GENERIC_IOMAP=y
CONFIG_ARCH_USE_CMPXCHG_LOCKREF=y
CONFIG_ARCH_HAS_FAST_MULTIPLIER=y
CONFIG_ARCH_USE_SYM_ANNOTATIONS=y

#
# Crypto library routines
#
CONFIG_CRYPTO_LIB_BLAKE2S_GENERIC=y
# CONFIG_CRYPTO_LIB_CURVE25519 is not set
CONFIG_CRYPTO_LIB_POLY1305_RSIZE=11
# CONFIG_CRYPTO_LIB_POLY1305 is not set
# end of Crypto library routines

# CONFIG_CRC_CCITT is not set
# CONFIG_CRC16 is not set
# CONFIG_CRC_T10DIF is not set
# CONFIG_CRC64_ROCKSOFT is not set
# CONFIG_CRC_ITU_T is not set
CONFIG_CRC32=y
# CONFIG_CRC32_SELFTEST is not set
CONFIG_CRC32_SLICEBY8=y
# CONFIG_CRC32_SLICEBY4 is not set
# CONFIG_CRC32_SARWATE is not set
# CONFIG_CRC32_BIT is not set
# CONFIG_CRC64 is not set
# CONFIG_CRC4 is not set
# CONFIG_CRC7 is not set
# CONFIG_LIBCRC32C is not set
# CONFIG_CRC8 is not set
# CONFIG_RANDOM32_SELFTEST is not set
# CONFIG_XZ_DEC is not set
CONFIG_HAS_IOMEM=y
CONFIG_HAS_IOPORT_MAP=y
CONFIG_HAS_DMA=y
CONFIG_NEED_SG_DMA_LENGTH=y
CONFIG_NEED_DMA_MAP_STATE=y
CONFIG_ARCH_DMA_ADDR_T_64BIT=y
CONFIG_SWIOTLB=y
# CONFIG_DMA_API_DEBUG is not set
# CONFIG_IRQ_POLL is not set
CONFIG_HAVE_GENERIC_VDSO=y
CONFIG_GENERIC_GETTIMEOFDAY=y
CONFIG_GENERIC_VDSO_TIME_NS=y
CONFIG_ARCH_HAS_PMEM_API=y
CONFIG_ARCH_HAS_UACCESS_FLUSHCACHE=y
CONFIG_ARCH_HAS_COPY_MC=y
CONFIG_ARCH_STACKWALK=y
CONFIG_STACKDEPOT=y
CONFIG_SBITMAP=y
# CONFIG_PARMAN is not set
# CONFIG_OBJAGG is not set
# end of Library routines

#
# Kernel hacking
#

#
# printk and dmesg options
#
# CONFIG_PRINTK_TIME is not set
# CONFIG_PRINTK_CALLER is not set
# CONFIG_STACKTRACE_BUILD_ID is not set
CONFIG_CONSOLE_LOGLEVEL_DEFAULT=7
CONFIG_CONSOLE_LOGLEVEL_QUIET=4
CONFIG_MESSAGE_LOGLEVEL_DEFAULT=4
# CONFIG_DYNAMIC_DEBUG is not set
# CONFIG_DYNAMIC_DEBUG_CORE is not set
# CONFIG_SYMBOLIC_ERRNAME is not set
CONFIG_DEBUG_BUGVERBOSE=y
# end of printk and dmesg options

# CONFIG_DEBUG_KERNEL is not set

#
# Compile-time checks and compiler options
#
CONFIG_FRAME_WARN=2048
# CONFIG_STRIP_ASM_SYMS is not set
# CONFIG_HEADERS_INSTALL is not set
CONFIG_DEBUG_SECTION_MISMATCH=y
CONFIG_SECTION_MISMATCH_WARN_ONLY=y
CONFIG_OBJTOOL=y
# end of Compile-time checks and compiler options

#
# Generic Kernel Debugging Instruments
#
# CONFIG_MAGIC_SYSRQ is not set
# CONFIG_DEBUG_FS is not set
CONFIG_HAVE_ARCH_KGDB=y
CONFIG_ARCH_HAS_UBSAN_SANITIZE_ALL=y
# CONFIG_UBSAN is not set
CONFIG_HAVE_ARCH_KCSAN=y
CONFIG_HAVE_KCSAN_COMPILER=y
# end of Generic Kernel Debugging Instruments

#
# Networking Debugging
#
# end of Networking Debugging

#
# Memory Debugging
#
# CONFIG_PAGE_EXTENSION is not set
CONFIG_SLUB_DEBUG=y
# CONFIG_SLUB_DEBUG_ON is not set
# CONFIG_PAGE_TABLE_CHECK is not set
# CONFIG_PAGE_POISONING is not set
# CONFIG_DEBUG_RODATA_TEST is not set
CONFIG_ARCH_HAS_DEBUG_WX=y
# CONFIG_DEBUG_WX is not set
CONFIG_GENERIC_PTDUMP=y
CONFIG_HAVE_DEBUG_KMEMLEAK=y
CONFIG_ARCH_HAS_DEBUG_VM_PGTABLE=y
# CONFIG_DEBUG_VM_PGTABLE is not set
CONFIG_ARCH_HAS_DEBUG_VIRTUAL=y
CONFIG_DEBUG_MEMORY_INIT=y
CONFIG_ARCH_SUPPORTS_KMAP_LOCAL_FORCE_MAP=y
CONFIG_HAVE_ARCH_KASAN=y
CONFIG_HAVE_ARCH_KASAN_VMALLOC=y
CONFIG_CC_HAS_KASAN_GENERIC=y
CONFIG_CC_HAS_WORKING_NOSANITIZE_ADDRESS=y
# CONFIG_KASAN is not set
CONFIG_HAVE_ARCH_KFENCE=y
# CONFIG_KFENCE is not set
# end of Memory Debugging

#
# Debug Oops, Lockups and Hangs
#
# CONFIG_PANIC_ON_OOPS is not set
CONFIG_PANIC_ON_OOPS_VALUE=0
CONFIG_PANIC_TIMEOUT=0
CONFIG_HARDLOCKUP_CHECK_TIMESTAMP=y
# end of Debug Oops, Lockups and Hangs

#
# Scheduler Debugging
#
# end of Scheduler Debugging

# CONFIG_DEBUG_TIMEKEEPING is not set

#
# Lock Debugging (spinlocks, mutexes, etc...)
#
CONFIG_LOCK_DEBUGGING_SUPPORT=y
# CONFIG_WW_MUTEX_SELFTEST is not set
# end of Lock Debugging (spinlocks, mutexes, etc...)

# CONFIG_DEBUG_IRQFLAGS is not set
CONFIG_STACKTRACE=y
# CONFIG_WARN_ALL_UNSEEDED_RANDOM is not set

#
# Debug kernel data structures
#
# CONFIG_BUG_ON_DATA_CORRUPTION is not set
# end of Debug kernel data structures

#
# RCU Debugging
#
# end of RCU Debugging

CONFIG_USER_STACKTRACE_SUPPORT=y
CONFIG_HAVE_RETHOOK=y
CONFIG_HAVE_FUNCTION_TRACER=y
CONFIG_HAVE_DYNAMIC_FTRACE=y
CONFIG_HAVE_DYNAMIC_FTRACE_WITH_REGS=y
CONFIG_HAVE_DYNAMIC_FTRACE_WITH_DIRECT_CALLS=y
CONFIG_HAVE_DYNAMIC_FTRACE_WITH_ARGS=y
CONFIG_HAVE_FTRACE_MCOUNT_RECORD=y
CONFIG_HAVE_SYSCALL_TRACEPOINTS=y
CONFIG_HAVE_FENTRY=y
CONFIG_HAVE_OBJTOOL_MCOUNT=y
CONFIG_HAVE_C_RECORDMCOUNT=y
CONFIG_HAVE_BUILDTIME_MCOUNT_SORT=y
CONFIG_TRACING_SUPPORT=y
# CONFIG_FTRACE is not set
# CONFIG_SAMPLES is not set
CONFIG_HAVE_SAMPLE_FTRACE_DIRECT=y
CONFIG_HAVE_SAMPLE_FTRACE_DIRECT_MULTI=y
CONFIG_ARCH_HAS_DEVMEM_IS_ALLOWED=y

#
# x86 Debugging
#
# CONFIG_X86_VERBOSE_BOOTUP is not set
CONFIG_EARLY_PRINTK=y
CONFIG_HAVE_MMIOTRACE_SUPPORT=y
CONFIG_IO_DELAY_0X80=y
# CONFIG_IO_DELAY_0XED is not set
# CONFIG_IO_DELAY_UDELAY is not set
# CONFIG_IO_DELAY_NONE is not set
CONFIG_UNWINDER_ORC=y
# CONFIG_UNWINDER_FRAME_POINTER is not set
# end of x86 Debugging

#
# Kernel Testing and Coverage
#
# CONFIG_KUNIT is not set
CONFIG_ARCH_HAS_KCOV=y
CONFIG_CC_HAS_SANCOV_TRACE_PC=y
# CONFIG_KCOV is not set
# CONFIG_RUNTIME_TESTING_MENU is not set
CONFIG_ARCH_USE_MEMTEST=y
# CONFIG_MEMTEST is not set
# end of Kernel Testing and Coverage

CONFIG_WARN_MISSING_DOCUMENTS=y
CONFIG_WARN_ABI_ERRORS=y
# end of Kernel hacking

^ permalink raw reply	[flat|nested] 13+ messages in thread

end of thread, other threads:[~2022-10-03  5:20 UTC | newest]

Thread overview: 13+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2022-09-19 18:55 [PATCH v2 0/3] Digiteq Automotive MGB4 driver tumic
2022-09-19 18:55 ` [PATCH v2 1/3] Added platform module alias for the xiic I2C driver tumic
2022-09-20 14:30   ` Michal Simek
2022-09-19 18:55 ` [PATCH v2 2/3] Added Xilinx XDMA IP core driver tumic
2022-09-20  2:12   ` kernel test robot
2022-09-21  6:24   ` Lizhi Hou
2022-09-21  9:25     ` Martin Tůma
2022-09-21 15:15       ` Lizhi Hou
2022-09-21 15:35         ` Martin Tůma
2022-09-19 18:55 ` [PATCH v2 3/3] Added Digiteq Automotive MGB4 driver tumic
2022-10-03  5:19   ` kernel test robot
2022-09-21  5:32 ` [PATCH v2 0/3] " Sonal Santan
2022-09-21  9:44   ` Martin Tůma

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).